Added ReadOptions; Changed proto; Reimplemented Log(er)

This commit is contained in:
Milos Gajdos 2019-11-27 17:31:35 +00:00
parent 9e177be560
commit 7deafbc5ce
No known key found for this signature in database
GPG Key ID: 8B31058CC55DFD4F
8 changed files with 195 additions and 115 deletions

View File

@ -17,7 +17,7 @@ var (
type Debug struct {
started int64
log.Logger
log.Log
}
func newDebug() *Debug {
@ -43,6 +43,6 @@ func (d *Debug) Stats(ctx context.Context, req *proto.StatsRequest, rsp *proto.S
return nil
}
func (d *Debug) Log(ctx context.Context, req *proto.LogRequest, rsp proto.Debug_LogStream) error {
func (d *Debug) Logs(ctx context.Context, req *proto.LogRequest, rsp proto.Debug_LogsStream) error {
return nil
}

View File

@ -12,13 +12,13 @@ var (
DefaultSize = 1000
)
// defaultLogger is default micro logger
type defaultLogger struct {
// defaultLog is default micro log
type defaultLog struct {
*buffer.Buffer
}
// NewLogger returns default Logger with
func NewLogger(opts ...Option) Logger {
// NewLog returns default Logger with
func NewLog(opts ...Option) Log {
// get default options
options := DefaultOptions()
@ -27,27 +27,43 @@ func NewLogger(opts ...Option) Logger {
o(&options)
}
return &defaultLogger{
return &defaultLog{
Buffer: buffer.New(options.Size),
}
}
// Write writes log into logger
func (l *defaultLogger) Write(v ...interface{}) {
l.log(fmt.Sprint(v...))
func (l *defaultLog) Write(v ...interface{}) {
l.Buffer.Put(fmt.Sprint(v...))
golog.Print(v...)
}
// Read reads logs from the logger
func (l *defaultLogger) Read(n int) []interface{} {
entries := l.Get(n)
vals := make([]interface{}, 0, len(entries))
for _, val := range entries {
vals = append(vals, val)
}
return vals
func (l *defaultLog) Read(opts ...ReadOption) []Record {
options := ReadOptions{}
// initialize the read options
for _, o := range opts {
o(&options)
}
func (l *defaultLogger) log(entry string) {
l.Buffer.Put(entry)
var entries []*buffer.Entry
// if Since options ha sbeen specified we honor it
if !options.Since.IsZero() {
entries = l.Buffer.Since(options.Since)
} else {
// otherwie return last count entries
entries = l.Buffer.Get(options.Count)
}
// TODO: if both Since and Count are set should we return
// last Count from the returned time scoped entries?
records := make([]Record, 0, len(entries))
for _, entry := range entries {
record := Record{
Timestamp: entry.Timestamp,
Value: entry.Value,
}
records = append(records, record)
}
return records
}

View File

@ -6,21 +6,27 @@ import (
)
func TestLogger(t *testing.T) {
// set size to some value
size := 100
// override the global logger
logger = NewLog(Size(size))
// make sure we have the right size of the logger ring buffer
if logger.(*defaultLogger).Size() != DefaultSize {
t.Errorf("expected buffer size: %d, got: %d", DefaultSize, logger.(*defaultLogger).Size())
if logger.(*defaultLog).Size() != size {
t.Errorf("expected buffer size: %d, got: %d", size, logger.(*defaultLog).Size())
}
// Log some cruft
Log("foobar")
Logf("foo %s", "bar")
Info("foobar")
// increase the log level
level = LevelDebug
Debugf("foo %s", "bar")
// Check if the logs are stored in the logger ring buffer
expectedEntries := []string{"foobar", "foo bar"}
entries := logger.Read(len(expectedEntries))
expected := []string{"foobar", "foo bar"}
entries := logger.Read(Count(len(expected)))
for i, entry := range entries {
if !reflect.DeepEqual(entry, expectedEntries[i]) {
t.Errorf("expected %s, got %s", expectedEntries[i], entry)
if !reflect.DeepEqual(entry.Value, expected[i]) {
t.Errorf("expected %s, got %s", expected[i], entry.Value)
}
}
}

View File

@ -4,25 +4,36 @@ package log
import (
"fmt"
"os"
"time"
)
var (
// logger is default Logger
logger Logger = NewLogger()
logger Log = NewLog()
// default log level is info
level = LevelInfo
// prefix for all messages
prefix string
)
// Logger is event logger
type Logger interface {
// Read reads specified number of log entries
Read(int) []interface{}
// Log is event log
type Log interface {
// Read reads log entries from the logger
Read(...ReadOption) []Record
// Write writes logs to logger
Write(...interface{})
}
// Record is log record entry
type Record struct {
// Timestamp of logged event
Timestamp time.Time
// Value contains log entry
Value interface{}
// Metadata to enrich log record
Metadata map[string]string
}
// level is a log level
type Level int
@ -52,7 +63,7 @@ func init() {
}
}
func Log(v ...interface{}) {
func log(v ...interface{}) {
if len(prefix) > 0 {
logger.Write(fmt.Sprint(append([]interface{}{prefix, " "}, v...)...))
return
@ -60,7 +71,7 @@ func Log(v ...interface{}) {
logger.Write(fmt.Sprint(v...))
}
func Logf(format string, v ...interface{}) {
func logf(format string, v ...interface{}) {
if len(prefix) > 0 {
format = prefix + " " + format
}
@ -72,7 +83,7 @@ func WithLevel(l Level, v ...interface{}) {
if l > level {
return
}
Log(v...)
log(v...)
}
// WithLevel logs with the level specified
@ -80,7 +91,7 @@ func WithLevelf(l Level, format string, v ...interface{}) {
if l > level {
return
}
Logf(format, v...)
logf(format, v...)
}
// Trace provides trace level logging
@ -145,11 +156,6 @@ func Fatalf(format string, v ...interface{}) {
os.Exit(1)
}
// GetLogger returns the local logger
func GetLogger() Logger {
return logger
}
// SetLevel sets the log level
func SetLevel(l Level) {
level = l

View File

@ -1,5 +1,7 @@
package log
import "time"
// Option used by the logger
type Option func(*Options)
@ -22,3 +24,28 @@ func DefaultOptions() 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
}
// 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
}
}

View File

@ -259,78 +259,100 @@ func (m *LogRequest) GetStream() bool {
return false
}
// LogEvent is service log event
type LogEvent struct {
// event log record
// TODO: change this
Record string `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"`
// Log is service log record
type Log struct {
// timestamp of log event
Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// log value
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
// metadata
Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LogEvent) Reset() { *m = LogEvent{} }
func (m *LogEvent) String() string { return proto.CompactTextString(m) }
func (*LogEvent) ProtoMessage() {}
func (*LogEvent) Descriptor() ([]byte, []int) {
func (m *Log) Reset() { *m = Log{} }
func (m *Log) String() string { return proto.CompactTextString(m) }
func (*Log) ProtoMessage() {}
func (*Log) Descriptor() ([]byte, []int) {
return fileDescriptor_8d9d361be58531fb, []int{5}
}
func (m *LogEvent) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LogEvent.Unmarshal(m, b)
func (m *Log) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Log.Unmarshal(m, b)
}
func (m *LogEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LogEvent.Marshal(b, m, deterministic)
func (m *Log) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Log.Marshal(b, m, deterministic)
}
func (m *LogEvent) XXX_Merge(src proto.Message) {
xxx_messageInfo_LogEvent.Merge(m, src)
func (m *Log) XXX_Merge(src proto.Message) {
xxx_messageInfo_Log.Merge(m, src)
}
func (m *LogEvent) XXX_Size() int {
return xxx_messageInfo_LogEvent.Size(m)
func (m *Log) XXX_Size() int {
return xxx_messageInfo_Log.Size(m)
}
func (m *LogEvent) XXX_DiscardUnknown() {
xxx_messageInfo_LogEvent.DiscardUnknown(m)
func (m *Log) XXX_DiscardUnknown() {
xxx_messageInfo_Log.DiscardUnknown(m)
}
var xxx_messageInfo_LogEvent proto.InternalMessageInfo
var xxx_messageInfo_Log proto.InternalMessageInfo
func (m *LogEvent) GetRecord() string {
func (m *Log) GetTimestamp() int64 {
if m != nil {
return m.Record
return m.Timestamp
}
return 0
}
func (m *Log) GetValue() string {
if m != nil {
return m.Value
}
return ""
}
func (m *Log) GetMetadata() map[string]string {
if m != nil {
return m.Metadata
}
return nil
}
func init() {
proto.RegisterType((*HealthRequest)(nil), "HealthRequest")
proto.RegisterType((*HealthResponse)(nil), "HealthResponse")
proto.RegisterType((*StatsRequest)(nil), "StatsRequest")
proto.RegisterType((*StatsResponse)(nil), "StatsResponse")
proto.RegisterType((*LogRequest)(nil), "LogRequest")
proto.RegisterType((*LogEvent)(nil), "LogEvent")
proto.RegisterType((*Log)(nil), "Log")
proto.RegisterMapType((map[string]string)(nil), "Log.MetadataEntry")
}
func init() { proto.RegisterFile("debug.proto", fileDescriptor_8d9d361be58531fb) }
var fileDescriptor_8d9d361be58531fb = []byte{
// 290 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xdd, 0x4a, 0xc3, 0x30,
0x14, 0xc7, 0xd7, 0x6d, 0x9d, 0xdb, 0x99, 0xeb, 0x20, 0x88, 0x84, 0x5d, 0x69, 0xae, 0x06, 0x42,
0x10, 0x7d, 0x05, 0x05, 0x2f, 0x76, 0x21, 0xf5, 0x09, 0xb2, 0xf6, 0x90, 0x0d, 0x6c, 0x33, 0x93,
0x53, 0xc1, 0x4b, 0xf1, 0xc5, 0x25, 0x5f, 0xe8, 0x2e, 0x7f, 0xff, 0xf6, 0x7c, 0xfd, 0x02, 0xcb,
0x16, 0xf7, 0x83, 0x96, 0x27, 0x6b, 0xc8, 0x88, 0x35, 0xac, 0x5e, 0x50, 0xbd, 0xd3, 0xa1, 0xc6,
0x8f, 0x01, 0x1d, 0x89, 0x2d, 0x54, 0x39, 0x70, 0x27, 0xd3, 0x3b, 0x64, 0xd7, 0x30, 0x73, 0xa4,
0x68, 0x70, 0xbc, 0xb8, 0x29, 0xb6, 0x8b, 0x3a, 0x91, 0xa8, 0xe0, 0xf2, 0x8d, 0x14, 0xb9, 0x5c,
0xf9, 0x53, 0xc0, 0x2a, 0x05, 0xa9, 0x92, 0xc3, 0x85, 0x23, 0x65, 0x09, 0xdb, 0x50, 0x3a, 0xad,
0x33, 0xfa, 0x9e, 0xc3, 0x89, 0x8e, 0x1d, 0xf2, 0x71, 0xf8, 0x90, 0xc8, 0xe7, 0x1d, 0x76, 0xc6,
0x7e, 0xf1, 0x49, 0xcc, 0x23, 0xf9, 0x4e, 0x74, 0xb0, 0xa8, 0x5a, 0xc7, 0xa7, 0xb1, 0x53, 0x42,
0x56, 0xc1, 0x58, 0x37, 0xbc, 0x0c, 0xe1, 0x58, 0x37, 0xe2, 0x15, 0x60, 0x67, 0x74, 0xda, 0x89,
0x5d, 0x41, 0xd9, 0x98, 0xa1, 0xa7, 0x34, 0x3f, 0x82, 0x4f, 0xdd, 0xb1, 0x6f, 0xf2, 0xf0, 0x08,
0xf1, 0x4e, 0x8b, 0xaa, 0x0b, 0xb3, 0xe7, 0x75, 0x22, 0x21, 0x60, 0xbe, 0x33, 0xfa, 0xf9, 0x13,
0x7b, 0xf2, 0xff, 0x58, 0x6c, 0x8c, 0x6d, 0xb3, 0x8b, 0x48, 0x0f, 0xdf, 0x05, 0x94, 0x4f, 0x5e,
0x2b, 0xbb, 0x83, 0x59, 0xf4, 0xc7, 0x2a, 0x79, 0x66, 0x76, 0xb3, 0x96, 0xe7, 0x62, 0xc5, 0x88,
0x6d, 0xa1, 0x0c, 0xc6, 0xd8, 0x4a, 0xfe, 0x57, 0xb9, 0xa9, 0xe4, 0x99, 0x48, 0x31, 0x62, 0xb7,
0x30, 0xd9, 0x19, 0xcd, 0x96, 0xf2, 0xef, 0xb8, 0xcd, 0x42, 0xe6, 0xbd, 0xc4, 0xe8, 0xbe, 0xd8,
0xcf, 0xc2, 0x8b, 0x3e, 0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x70, 0x27, 0xbe, 0x8c, 0xe0, 0x01,
0x00, 0x00,
// 359 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xcd, 0x6a, 0xe3, 0x30,
0x14, 0x85, 0xe3, 0xbf, 0x4c, 0x72, 0x33, 0x76, 0x06, 0x31, 0x0c, 0xc6, 0xcc, 0x40, 0xd0, 0xca,
0x30, 0x20, 0x86, 0xcc, 0xa6, 0xb4, 0xdb, 0x16, 0xba, 0x70, 0xa1, 0xb8, 0x4f, 0xa0, 0xd8, 0xc2,
0x09, 0x8d, 0x2d, 0xd7, 0xba, 0x2e, 0x64, 0xd1, 0x55, 0xdf, 0xa5, 0xcf, 0x59, 0x64, 0xc9, 0x4d,
0x0d, 0xdd, 0xf9, 0x3b, 0xf2, 0x3d, 0x47, 0xbe, 0xc7, 0xb0, 0x2a, 0xc5, 0xae, 0xaf, 0x58, 0xdb,
0x49, 0x94, 0x74, 0x0d, 0xe1, 0xad, 0xe0, 0x47, 0xdc, 0xe7, 0xe2, 0xa9, 0x17, 0x0a, 0x69, 0x0a,
0xd1, 0x28, 0xa8, 0x56, 0x36, 0x4a, 0x90, 0x5f, 0x30, 0x57, 0xc8, 0xb1, 0x57, 0xb1, 0xb3, 0x71,
0xd2, 0x65, 0x6e, 0x89, 0x46, 0xf0, 0xfd, 0x01, 0x39, 0xaa, 0x71, 0xf2, 0xd5, 0x81, 0xd0, 0x0a,
0x76, 0x32, 0x86, 0x6f, 0x0a, 0x79, 0x87, 0xa2, 0x1c, 0x46, 0xfd, 0x7c, 0x44, 0xed, 0xd9, 0xb7,
0x78, 0xa8, 0x45, 0xec, 0x0e, 0x07, 0x96, 0xb4, 0x5e, 0x8b, 0x5a, 0x76, 0xa7, 0xd8, 0x33, 0xba,
0x21, 0xed, 0x84, 0xfb, 0x4e, 0xf0, 0x52, 0xc5, 0xbe, 0x71, 0xb2, 0x48, 0x22, 0x70, 0xab, 0x22,
0x0e, 0x06, 0xd1, 0xad, 0x0a, 0x7a, 0x0f, 0x90, 0xc9, 0xca, 0xde, 0x89, 0xfc, 0x84, 0xa0, 0x90,
0x7d, 0x83, 0x36, 0xdf, 0x80, 0x56, 0xd5, 0xa1, 0x29, 0xc6, 0x70, 0x03, 0xe6, 0x3b, 0x3b, 0xc1,
0xeb, 0x21, 0x7b, 0x91, 0x5b, 0xa2, 0x6f, 0x0e, 0x78, 0x99, 0xac, 0xc8, 0x6f, 0x58, 0xea, 0x3b,
0x2a, 0xe4, 0x75, 0x3b, 0xf8, 0x79, 0xf9, 0x59, 0xd0, 0x9e, 0xcf, 0xfc, 0xd8, 0x1b, 0xcf, 0x65,
0x6e, 0x80, 0x30, 0x58, 0xd4, 0x02, 0x79, 0xc9, 0x91, 0xc7, 0xde, 0xc6, 0x4b, 0x57, 0x5b, 0xc2,
0x32, 0x59, 0xb1, 0x3b, 0x2b, 0xde, 0x34, 0xd8, 0x9d, 0xf2, 0x8f, 0x77, 0x92, 0x2b, 0x08, 0x27,
0x47, 0xe4, 0x07, 0x78, 0x8f, 0xe2, 0x64, 0x37, 0xaf, 0x1f, 0xbf, 0x0e, 0xba, 0x74, 0x2f, 0x9c,
0xed, 0x0b, 0x04, 0xd7, 0xba, 0x5a, 0xf2, 0x17, 0xe6, 0xa6, 0x43, 0x12, 0xb1, 0x49, 0xbb, 0xc9,
0x9a, 0x4d, 0xcb, 0xa5, 0x33, 0x92, 0x42, 0x30, 0xb4, 0x46, 0x42, 0xf6, 0xb9, 0xce, 0x24, 0x62,
0x93, 0x32, 0xe9, 0x8c, 0xfc, 0x01, 0x3f, 0x93, 0x95, 0x22, 0x2b, 0x76, 0xde, 0x70, 0xe2, 0x6b,
0xa0, 0xb3, 0x7f, 0xce, 0x6e, 0x3e, 0xfc, 0x51, 0xff, 0xdf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xbe,
0xa2, 0x1f, 0x47, 0x60, 0x02, 0x00, 0x00,
}

View File

@ -36,7 +36,7 @@ var _ server.Option
type DebugService interface {
Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error)
Stats(ctx context.Context, in *StatsRequest, opts ...client.CallOption) (*StatsResponse, error)
Log(ctx context.Context, in *LogRequest, opts ...client.CallOption) (Debug_LogService, error)
Logs(ctx context.Context, in *LogRequest, opts ...client.CallOption) (Debug_LogsService, error)
}
type debugService struct {
@ -77,8 +77,8 @@ func (c *debugService) Stats(ctx context.Context, in *StatsRequest, opts ...clie
return out, nil
}
func (c *debugService) Log(ctx context.Context, in *LogRequest, opts ...client.CallOption) (Debug_LogService, error) {
req := c.c.NewRequest(c.name, "Debug.Log", &LogRequest{})
func (c *debugService) Logs(ctx context.Context, in *LogRequest, opts ...client.CallOption) (Debug_LogsService, error) {
req := c.c.NewRequest(c.name, "Debug.Logs", &LogRequest{})
stream, err := c.c.Stream(ctx, req, opts...)
if err != nil {
return nil, err
@ -86,34 +86,34 @@ func (c *debugService) Log(ctx context.Context, in *LogRequest, opts ...client.C
if err := stream.Send(in); err != nil {
return nil, err
}
return &debugServiceLog{stream}, nil
return &debugServiceLogs{stream}, nil
}
type Debug_LogService interface {
type Debug_LogsService interface {
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Recv() (*LogEvent, error)
Recv() (*Log, error)
}
type debugServiceLog struct {
type debugServiceLogs struct {
stream client.Stream
}
func (x *debugServiceLog) Close() error {
func (x *debugServiceLogs) Close() error {
return x.stream.Close()
}
func (x *debugServiceLog) SendMsg(m interface{}) error {
func (x *debugServiceLogs) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *debugServiceLog) RecvMsg(m interface{}) error {
func (x *debugServiceLogs) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *debugServiceLog) Recv() (*LogEvent, error) {
m := new(LogEvent)
func (x *debugServiceLogs) Recv() (*Log, error) {
m := new(Log)
err := x.stream.Recv(m)
if err != nil {
return nil, err
@ -126,14 +126,14 @@ func (x *debugServiceLog) Recv() (*LogEvent, error) {
type DebugHandler interface {
Health(context.Context, *HealthRequest, *HealthResponse) error
Stats(context.Context, *StatsRequest, *StatsResponse) error
Log(context.Context, *LogRequest, Debug_LogStream) error
Logs(context.Context, *LogRequest, Debug_LogsStream) error
}
func RegisterDebugHandler(s server.Server, hdlr DebugHandler, opts ...server.HandlerOption) error {
type debug interface {
Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error
Stats(ctx context.Context, in *StatsRequest, out *StatsResponse) error
Log(ctx context.Context, stream server.Stream) error
Logs(ctx context.Context, stream server.Stream) error
}
type Debug struct {
debug
@ -154,37 +154,37 @@ func (h *debugHandler) Stats(ctx context.Context, in *StatsRequest, out *StatsRe
return h.DebugHandler.Stats(ctx, in, out)
}
func (h *debugHandler) Log(ctx context.Context, stream server.Stream) error {
func (h *debugHandler) Logs(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})
return h.DebugHandler.Logs(ctx, m, &debugLogsStream{stream})
}
type Debug_LogStream interface {
type Debug_LogsStream interface {
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*LogEvent) error
Send(*Log) error
}
type debugLogStream struct {
type debugLogsStream struct {
stream server.Stream
}
func (x *debugLogStream) Close() error {
func (x *debugLogsStream) Close() error {
return x.stream.Close()
}
func (x *debugLogStream) SendMsg(m interface{}) error {
func (x *debugLogsStream) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *debugLogStream) RecvMsg(m interface{}) error {
func (x *debugLogsStream) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *debugLogStream) Send(m *LogEvent) error {
func (x *debugLogsStream) Send(m *Log) error {
return x.stream.Send(m)
}

View File

@ -3,7 +3,7 @@ syntax = "proto3";
service Debug {
rpc Health(HealthRequest) returns (HealthResponse) {};
rpc Stats(StatsRequest) returns (StatsResponse) {};
rpc Log(LogRequest) returns (stream LogEvent) {};
rpc Logs(LogRequest) returns (stream Log) {};
}
message HealthRequest {}
@ -40,9 +40,12 @@ message LogRequest {
bool stream = 3;
}
// LogEvent is service log event
message LogEvent {
// event log record
// TODO: change this
string record = 1;
// Log is service log record
message Log {
// timestamp of log event
int64 timestamp = 1;
// log value
string value = 2;
// metadata
map<string,string> metadata = 3;
}