func (r *localRuntime) Stop() error {
	r.Lock()
	defer r.Unlock()
	if !r.running {
		return nil
	}
	r.running = false
	for _, services := range r.namespaces {
		for _, service := range services {
			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
				logger.Debugf("Runtime stopping %s", service.Name)
			}
			service.Stop()
		}
	}
	return nil
}
Stop stops the runtime
package local
import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"github.com/hpcloud/tail"
	"github.com/micro/micro/v3/service/logger"
	"github.com/micro/micro/v3/service/runtime"
)
const defaultNamespace = "micro"
var (
	LogDir = filepath.Join(os.TempDir(), "micro", "logs")
	SourceDir = filepath.Join(os.TempDir(), "micro", "uploads")
)
type localRuntime struct {
	sync.RWMutex
	options runtime.Options
	start chan *service
	running bool
	namespaces map[string]map[string]*service
}
func NewRuntime(opts ...runtime.Option) runtime.Runtime {
	options := runtime.Options{}
	for _, o := range opts {
		o(&options)
	}
	os.MkdirAll(LogDir, 0755)
	return &localRuntime{
		options:    options,
		start:      make(chan *service, 128),
		namespaces: make(map[string]map[string]*service),
	}
}
func (r *localRuntime) Init(opts ...runtime.Option) error {
	r.Lock()
	defer r.Unlock()
	for _, o := range opts {
		o(&r.options)
	}
	return nil
}
func logFile(serviceName string) string {
	name := strings.Replace(serviceName, "/", "-", -1)
	return filepath.Join(LogDir, fmt.Sprintf("%v.log", name))
}
func serviceKey(s *runtime.Service) string {
	return fmt.Sprintf("%v:%v", s.Name, s.Version)
}
func (r *localRuntime) Create(resource runtime.Resource, opts ...runtime.CreateOption) error {
	var options runtime.CreateOptions
	for _, o := range opts {
		o(&options)
	}
	r.Lock()
	defer r.Unlock()
	switch resource.Type() {
	case runtime.TypeNamespace:
		return nil
	case runtime.TypeNetworkPolicy:
		return nil
	case runtime.TypeResourceQuota:
		return nil
	case runtime.TypeService:
		s, ok := resource.(*runtime.Service)
		if !ok {
			return runtime.ErrInvalidResource
		}
		if len(options.Namespace) == 0 {
			options.Namespace = defaultNamespace
		}
		if len(options.Entrypoint) > 0 {
			s.Source = filepath.Join(s.Source, options.Entrypoint)
		}
		if len(options.Command) == 0 {
			options.Command = []string{"go"}
			if _, err := os.Stat(filepath.Join(s.Source, "vendor")); err == nil {
				options.Args = []string{"run", "-mod", "vendor", "."}
			} else {
				options.Args = []string{"run", "."}
			}
		}
		for key, value := range options.Secrets {
			options.Env = append(options.Env, fmt.Sprintf("%v=%v", key, value))
		}
		if _, ok := r.namespaces[options.Namespace]; !ok {
			r.namespaces[options.Namespace] = make(map[string]*service)
		}
		if _, ok := r.namespaces[options.Namespace][serviceKey(s)]; ok {
			return runtime.ErrAlreadyExists
		}
		service := newService(s, options)
		f, err := os.OpenFile(logFile(service.Name), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			log.Fatal(err)
		}
		if service.output != nil {
			service.output = io.MultiWriter(service.output, f)
		} else {
			service.output = f
		}
		if err := service.Start(); err != nil {
			return err
		}
		r.namespaces[options.Namespace][serviceKey(s)] = service
		return nil
	default:
		return runtime.ErrInvalidResource
	}
}
func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}
func (r *localRuntime) Logs(resource runtime.Resource, options ...runtime.LogsOption) (runtime.LogStream, error) {
	lopts := runtime.LogsOptions{}
	for _, o := range options {
		o(&lopts)
	}
	switch resource.Type() {
	case runtime.TypeNamespace:
		return nil, nil
	case runtime.TypeNetworkPolicy:
		return nil, nil
	case runtime.TypeResourceQuota:
		return nil, nil
	case runtime.TypeService:
		s, ok := resource.(*runtime.Service)
		if !ok {
			return nil, runtime.ErrInvalidResource
		}
		ret := &logStream{
			service: s.Name,
			stream:  make(chan runtime.Log),
			stop:    make(chan bool),
		}
		fpath := logFile(s.Name)
		if ex, err := exists(fpath); err != nil {
			return nil, err
		} else if !ex {
			return nil, fmt.Errorf("Logs not found for service %s", s.Name)
		}
		fi, err := os.Stat(fpath)
		if err != nil {
			return nil, err
		}
		size := fi.Size()
		whence := 2
		offset := lopts.Count * 200
		if offset > size {
			offset = size
		}
		offset *= -1
		t, err := tail.TailFile(fpath, tail.Config{Follow: lopts.Stream, Location: &tail.SeekInfo{
			Whence: whence,
			Offset: int64(offset),
		}, Logger: tail.DiscardingLogger})
		if err != nil {
			return nil, err
		}
		ret.tail = t
		go func() {
			for {
				select {
				case line, ok := <-t.Lines:
					if !ok {
						ret.Stop()
						return
					}
					ret.stream <- runtime.Log{Message: line.Text}
				case <-ret.stop:
					return
				}
			}
		}()
		return ret, nil
	default:
		return nil, runtime.ErrInvalidResource
	}
}
type logStream struct {
	tail    *tail.Tail
	service string
	stream  chan runtime.Log
	sync.Mutex
	stop chan bool
	err  error
}
func (l *logStream) Chan() chan runtime.Log {
	return l.stream
}
func (l *logStream) Error() error {
	return l.err
}
func (l *logStream) Stop() error {
	l.Lock()
	defer l.Unlock()
	select {
	case <-l.stop:
		return nil
	default:
		close(l.stop)
		close(l.stream)
		err := l.tail.Stop()
		if err != nil {
			logger.Errorf("Error stopping tail: %v", err)
			return err
		}
	}
	return nil
}
func (r *localRuntime) Read(opts ...runtime.ReadOption) ([]*runtime.Service, error) {
	r.Lock()
	defer r.Unlock()
	gopts := runtime.ReadOptions{}
	for _, o := range opts {
		o(&gopts)
	}
	if len(gopts.Namespace) == 0 {
		gopts.Namespace = defaultNamespace
	}
	save := func(k, v string) bool {
		if len(k) == 0 {
			return true
		}
		return k == v
	}
	var services []*runtime.Service
	if _, ok := r.namespaces[gopts.Namespace]; !ok {
		return make([]*runtime.Service, 0), nil
	}
	for _, service := range r.namespaces[gopts.Namespace] {
		if !save(gopts.Service, service.Name) {
			continue
		}
		if !save(gopts.Version, service.Version) {
			continue
		}
		services = append(services, service.Service)
	}
	return services, nil
}
func (r *localRuntime) Update(resource runtime.Resource, opts ...runtime.UpdateOption) error {
	var options runtime.UpdateOptions
	for _, o := range opts {
		o(&options)
	}
	switch resource.Type() {
	case runtime.TypeNamespace:
		return nil
	case runtime.TypeNetworkPolicy:
		return nil
	case runtime.TypeResourceQuota:
		return nil
	case runtime.TypeService:
		s, ok := resource.(*runtime.Service)
		if !ok {
			return runtime.ErrInvalidResource
		}
		if len(options.Entrypoint) > 0 {
			s.Source = filepath.Join(s.Source, options.Entrypoint)
		}
		if len(options.Namespace) == 0 {
			options.Namespace = defaultNamespace
		}
		r.Lock()
		srvs, ok := r.namespaces[options.Namespace]
		r.Unlock()
		if !ok {
			return errors.New("Service not found")
		}
		r.Lock()
		service, ok := srvs[serviceKey(s)]
		r.Unlock()
		if !ok {
			return errors.New("Service not found")
		}
		if err := service.Stop(); err != nil && err.Error() != "no such process" {
			logger.Errorf("Error stopping service %s: %s", service.Name, err)
			return err
		}
		service.Source = s.Source
		service.Exec.Dir = s.Source
		return service.Start()
	default:
		return runtime.ErrInvalidResource
	}
}
func (r *localRuntime) Delete(resource runtime.Resource, opts ...runtime.DeleteOption) error {
	switch resource.Type() {
	case runtime.TypeNamespace:
		return nil
	case runtime.TypeNetworkPolicy:
		return nil
	case runtime.TypeResourceQuota:
		return nil
	case runtime.TypeService:
		s, ok := resource.(*runtime.Service)
		if !ok {
			return runtime.ErrInvalidResource
		}
		r.Lock()
		defer r.Unlock()
		var options runtime.DeleteOptions
		for _, o := range opts {
			o(&options)
		}
		if len(options.Namespace) == 0 {
			options.Namespace = defaultNamespace
		}
		srvs, ok := r.namespaces[options.Namespace]
		if !ok {
			return nil
		}
		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
			logger.Debugf("Runtime deleting service %s", s.Name)
		}
		service, ok := srvs[serviceKey(s)]
		if !ok {
			return nil
		}
		if !service.Running() {
			delete(srvs, service.key())
			r.namespaces[options.Namespace] = srvs
			return nil
		}
		if err := service.Stop(); err != nil {
			return err
		}
		delete(srvs, service.key())
		r.namespaces[options.Namespace] = srvs
		return nil
	default:
		return runtime.ErrInvalidResource
	}
}
func (r *localRuntime) Start() error {
	r.Lock()
	defer r.Unlock()
	if r.running {
		return nil
	}
	r.running = true
	return nil
}