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
	}
}
@todo: Getting existing lines is not supported yet.
The reason for this is because it's hard to calculate line offset
as opposed to character offset.
This logger streams by default and only supports the `StreamCount` option.
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
}