Merge pull request #318 from fnproject/fnlog-fiddler

make fn logger more reasonable
This commit is contained in:
Reed Allman
2017-09-18 13:59:31 -07:00
committed by GitHub
9 changed files with 126 additions and 114 deletions

View File

@@ -32,9 +32,7 @@ import (
// TODO herd launch prevention part deux // TODO herd launch prevention part deux
// TODO storing logs / call can push call over the timeout // TODO storing logs / call can push call over the timeout
// TODO all Datastore methods need to take unit of tenancy (app or route) at least (e.g. not just call id) // TODO all Datastore methods need to take unit of tenancy (app or route) at least (e.g. not just call id)
// TODO limit the request body length when making calls
// TODO discuss concrete policy for hot launch or timeout / timeout vs time left // TODO discuss concrete policy for hot launch or timeout / timeout vs time left
// TODO call env need to be map[string][]string to match headers behavior...
// TODO it may be nice to have an interchange type for Dispatch that can have // TODO it may be nice to have an interchange type for Dispatch that can have
// all the info we need to build e.g. http req, grpc req, json, etc. so that // all the info we need to build e.g. http req, grpc req, json, etc. so that
// we can easily do e.g. http->grpc, grpc->http, http->json. ofc grpc<->http is // we can easily do e.g. http->grpc, grpc->http, http->json. ofc grpc<->http is
@@ -50,7 +48,6 @@ import (
// end up that the client doesn't get a reply until long after the timeout (b/c of container removal, async it?) // end up that the client doesn't get a reply until long after the timeout (b/c of container removal, async it?)
// TODO the call api should fill in all the fields // TODO the call api should fill in all the fields
// TODO the log api should be plaintext (or at least offer it) // TODO the log api should be plaintext (or at least offer it)
// TODO func logger needs to be hanged, dragged and quartered. in reverse order.
// TODO we should probably differentiate ran-but-timeout vs timeout-before-run // TODO we should probably differentiate ran-but-timeout vs timeout-before-run
// TODO between calls, logs and stderr can contain output/ids from previous call. need elegant solution. grossness. // TODO between calls, logs and stderr can contain output/ids from previous call. need elegant solution. grossness.
// TODO if async would store requests (or interchange format) it would be slick, but // TODO if async would store requests (or interchange format) it would be slick, but
@@ -428,7 +425,6 @@ type slot interface {
type coldSlot struct { type coldSlot struct {
cookie drivers.Cookie cookie drivers.Cookie
tok Token tok Token
stderr io.Closer
} }
func (s *coldSlot) exec(ctx context.Context, call *call) error { func (s *coldSlot) exec(ctx context.Context, call *call) error {
@@ -459,7 +455,6 @@ func (s *coldSlot) Close() error {
s.cookie.Close(context.Background()) // ensure container removal, separate ctx s.cookie.Close(context.Background()) // ensure container removal, separate ctx
} }
s.tok.Close() s.tok.Close()
s.stderr.Close()
return nil return nil
} }
@@ -477,20 +472,11 @@ func (s *hotSlot) exec(ctx context.Context, call *call) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "agent_hot_exec") span, ctx := opentracing.StartSpanFromContext(ctx, "agent_hot_exec")
defer span.Finish() defer span.Finish()
stderr := NewFuncLogger(ctx, call.AppName, call.Path, call.Image, call.ID, call.ds)
if call.w == nil {
// send STDOUT to logs if no writer given (async...)
// TODO fuck func logger, change it to not need a context and make calls
// require providing their own stderr and writer instead of this crap. punting atm.
call.w = stderr
}
// link the container id and id in the logs [for us!] // link the container id and id in the logs [for us!]
common.Logger(ctx).WithField("container_id", s.container.id).Info("starting call") common.Logger(ctx).WithField("container_id", s.container.id).Info("starting call")
// swap in the new id and the new stderr logger // swap in the new stderr logger
s.container.swap(stderr) s.container.swap(call.stderr)
defer stderr.Close() // TODO shove in Close / elsewhere (to upload logs after exec exits)
errApp := make(chan error, 1) errApp := make(chan error, 1)
go func() { go func() {
@@ -541,15 +527,6 @@ func (a *agent) launch(ctx context.Context, slots chan<- slot, call *call, tok T
} }
func (a *agent) prepCold(ctx context.Context, slots chan<- slot, call *call, tok Token) error { func (a *agent) prepCold(ctx context.Context, slots chan<- slot, call *call, tok Token) error {
// TODO dupe stderr code, reduce me
stderr := NewFuncLogger(ctx, call.AppName, call.Path, call.Image, call.ID, call.ds)
if call.w == nil {
// send STDOUT to logs if no writer given (async...)
// TODO fuck func logger, change it to not need a context and make calls
// require providing their own stderr and writer instead of this crap. punting atm.
call.w = stderr
}
container := &container{ container := &container{
id: id.New().String(), // XXX we could just let docker generate ids... id: id.New().String(), // XXX we could just let docker generate ids...
image: call.Image, image: call.Image,
@@ -558,7 +535,7 @@ func (a *agent) prepCold(ctx context.Context, slots chan<- slot, call *call, tok
timeout: time.Duration(call.Timeout) * time.Second, // this is unnecessary, but in case removal fails... timeout: time.Duration(call.Timeout) * time.Second, // this is unnecessary, but in case removal fails...
stdin: call.req.Body, stdin: call.req.Body,
stdout: call.w, stdout: call.w,
stderr: stderr, stderr: call.stderr,
} }
// pull & create container before we return a slot, so as to be friendly // pull & create container before we return a slot, so as to be friendly
@@ -569,7 +546,7 @@ func (a *agent) prepCold(ctx context.Context, slots chan<- slot, call *call, tok
return err return err
} }
slot := &coldSlot{cookie, tok, stderr} slot := &coldSlot{cookie, tok}
select { select {
case slots <- slot: // TODO need to make sure receiver will be ready (go routine race) case slots <- slot: // TODO need to make sure receiver will be ready (go routine race)
default: default:
@@ -600,19 +577,22 @@ func (a *agent) runHot(slots chan<- slot, call *call, tok Token) error {
ctx, shutdownContainer := context.WithCancel(context.Background()) ctx, shutdownContainer := context.WithCancel(context.Background())
defer shutdownContainer() // close this if our waiter returns defer shutdownContainer() // close this if our waiter returns
cid := id.New().String()
// set up the stderr for the first one to capture any logs before the slot is // set up the stderr for the first one to capture any logs before the slot is
// executed. // executed and between hot functions TODO this is still a little tobias funke
// TODO need to figure out stderr logging for hot functions at a high level stderr := newLineWriter(&logWriter{
stderr := &ghostWriter{inner: newLineWriter(&logWriter{ctx: ctx, appName: call.AppName, path: call.Path, image: call.Image, reqID: call.ID})} logrus.WithFields(logrus.Fields{"between_log": true, "app_name": call.AppName, "path": call.Path, "image": call.Image, "container_id": cid}),
})
container := &container{ container := &container{
id: id.New().String(), // XXX we could just let docker generate ids... id: cid, // XXX we could just let docker generate ids...
image: call.Image, image: call.Image,
env: call.BaseEnv, // only base env env: call.BaseEnv, // only base env
memory: call.Memory, memory: call.Memory,
stdin: stdinRead, stdin: stdinRead,
stdout: stdoutWrite, stdout: stdoutWrite,
stderr: stderr, stderr: &ghostWriter{inner: stderr},
} }
logger := logrus.WithFields(logrus.Fields{"id": container.id, "app": call.AppName, "route": call.Path, "image": call.Image, "memory": call.Memory, "format": call.Format, "idle_timeout": call.IdleTimeout}) logger := logrus.WithFields(logrus.Fields{"id": container.id, "app": call.AppName, "route": call.Path, "image": call.Image, "memory": call.Memory, "format": call.Format, "idle_timeout": call.IdleTimeout})
@@ -663,6 +643,7 @@ func (a *agent) runHot(slots chan<- slot, call *call, tok Token) error {
// wait for this call to finish // wait for this call to finish
// NOTE do NOT select with shutdown / other channels. slot handles this. // NOTE do NOT select with shutdown / other channels. slot handles this.
<-done <-done
container.swap(stderr) // log between tasks
} }
}() }()

View File

@@ -2,6 +2,7 @@ package agent
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -13,6 +14,7 @@ import (
"github.com/fnproject/fn/api/datastore" "github.com/fnproject/fn/api/datastore"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/mqs" "github.com/fnproject/fn/api/mqs"
"github.com/sirupsen/logrus"
) )
func TestCallConfigurationRequest(t *testing.T) { func TestCallConfigurationRequest(t *testing.T) {
@@ -273,3 +275,28 @@ func TestCallConfigurationModel(t *testing.T) {
t.Fatal("expected payload to match, but it was a lie") t.Fatal("expected payload to match, but it was a lie")
} }
} }
func TestLoggerIsStringerAndWorks(t *testing.T) {
// TODO test limit writer, logrus writer, etc etc
loggyloo := logrus.WithFields(logrus.Fields{"yodawg": true})
logger := setupLogger(loggyloo)
if _, ok := logger.(fmt.Stringer); !ok {
// NOTE: if you are reading, maybe what you've done is ok, but be aware we were relying on this for optimization...
t.Fatal("you turned the logger into something inefficient and possibly better all at the same time, how dare ye!")
}
str := "0 line\n1 line\n2 line\n\n4 line"
logger.Write([]byte(str))
strGot := logger.(fmt.Stringer).String()
if strGot != str {
t.Fatal("logs did not match expectations, like being an adult", strGot, str)
}
logger.Close() // idk maybe this would panic might as well call this
// TODO we could check for the toilet to flush here to logrus
}

View File

@@ -246,7 +246,6 @@ func (a *agent) GetCall(opts ...CallOpt) (Call, error) {
return nil, errors.New("no model or request provided for call") return nil, errors.New("no model or request provided for call")
} }
// TODO move func logger here
// TODO add log store interface (yagni?) // TODO add log store interface (yagni?)
c.ds = a.ds c.ds = a.ds
c.mq = a.mq c.mq = a.mq
@@ -255,6 +254,15 @@ func (a *agent) GetCall(opts ...CallOpt) (Call, error) {
logrus.Fields{"id": c.ID, "app": c.AppName, "route": c.Path}) logrus.Fields{"id": c.ID, "app": c.AppName, "route": c.Path})
c.req = c.req.WithContext(ctx) c.req = c.req.WithContext(ctx)
// setup stderr logger separate (don't inherit ctx vars)
logger := logrus.WithFields(logrus.Fields{"user_log": true, "app_name": c.AppName, "path": c.Path, "image": c.Image, "call_id": c.ID})
c.stderr = setupLogger(logger)
if c.w == nil {
// send STDOUT to logs if no writer given (async...)
// TODO we could/should probably make this explicit to GetCall, ala 'WithLogger', but it's dupe code (who cares?)
c.w = c.stderr
}
return &c, nil return &c, nil
} }
@@ -265,7 +273,7 @@ type call struct {
mq models.MessageQueue mq models.MessageQueue
w io.Writer w io.Writer
req *http.Request req *http.Request
stderr io.WriteCloser stderr io.ReadWriteCloser
} }
func (c *call) Model() *models.Call { return c.Call } func (c *call) Model() *models.Call { return c.Call }
@@ -335,8 +343,15 @@ func (c *call) End(ctx context.Context, err error) {
// call that ran successfully [by a user's perspective] // call that ran successfully [by a user's perspective]
// TODO: this should be update, really // TODO: this should be update, really
if err := c.ds.InsertCall(ctx, c.Call); err != nil { if err := c.ds.InsertCall(ctx, c.Call); err != nil {
logrus.WithError(err).Error("error inserting call into datastore") common.Logger(ctx).WithError(err).Error("error inserting call into datastore")
} }
if err := c.ds.InsertLog(ctx, c.AppName, c.ID, c.stderr); err != nil {
common.Logger(ctx).WithError(err).Error("error uploading log")
}
// NOTE call this after InsertLog or the buffer will get reset
c.stderr.Close()
} }
func fakeHandler(http.ResponseWriter, *http.Request, Params) {} func fakeHandler(http.ResponseWriter, *http.Request, Params) {}

View File

@@ -2,15 +2,11 @@ package agent
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"sync" "sync"
"github.com/fnproject/fn/api/common"
"github.com/fnproject/fn/api/models"
"github.com/opentracing/opentracing-go"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@@ -19,13 +15,10 @@ var (
logPool = &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }} logPool = &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
) )
// TODO we can have different types of these func loggers later // setupLogger returns an io.ReadWriteCloser which may write to multiple io.Writer's,
// TODO move this to a different package // and may be read from the returned io.Reader (singular). After Close is called,
// the Reader is not safe to read from, nor the Writer to write to.
// DefaultFuncLogger returns a WriteCloser that writes STDERR output from a func setupLogger(logger logrus.FieldLogger) io.ReadWriteCloser {
// container and outputs it in a parsed structured log format to attached
// STDERR as well as writing the log to the db when Close is called.
func NewFuncLogger(ctx context.Context, appName, path, image, reqID string, logDB models.LogStore) io.WriteCloser {
lbuf := bufPool.Get().(*bytes.Buffer) lbuf := bufPool.Get().(*bytes.Buffer)
dbuf := logPool.Get().(*bytes.Buffer) dbuf := logPool.Get().(*bytes.Buffer)
@@ -39,34 +32,33 @@ func NewFuncLogger(ctx context.Context, appName, path, image, reqID string, logD
} }
// we don't need to limit the log writer, but we do need it to dispense lines // we don't need to limit the log writer, but we do need it to dispense lines
linew := newLineWriterWithBuffer(lbuf, &logWriter{ linew := newLineWriterWithBuffer(lbuf, &logWriter{logger})
ctx: ctx,
appName: appName,
path: path,
image: image,
reqID: reqID,
})
const MB = 1 * 1024 * 1024 // pick a number any number.. TODO configurable ? const MB = 1 * 1024 * 1024 // pick a number any number.. TODO configurable ?
// we don't need to log per line to db, but we do need to limit it // we don't need to log per line to db, but we do need to limit it
limitw := newLimitWriter(MB, &dbWriter{ limitw := &nopCloser{newLimitWriter(MB, dbuf)}
Buffer: dbuf,
db: logDB,
ctx: ctx,
reqID: reqID,
appName: appName,
})
// TODO / NOTE: we want linew to be first because limitw may error if limit // TODO / NOTE: we want linew to be first because limitw may error if limit
// is reached but we still want to log. we should probably ignore hitting the // is reached but we still want to log. we should probably ignore hitting the
// limit error since we really just want to not write too much to db and // limit error since we really just want to not write too much to db and
// that's handled as is. put buffers back last to avoid misuse, if there's // that's handled as is. put buffers back last to avoid misuse, if there's
// an error they won't get put back and that's really okay too. // an error they won't get put back and that's really okay too.
return multiWriteCloser{linew, limitw, &fCloser{close}} mw := multiWriteCloser{linew, limitw, &fCloser{close}}
return &rwc{mw, dbuf}
} }
// implements passthrough Write & arbitrary func close to have a seat at the cool kids lunch table // implements io.ReadWriteCloser, keeps the buffer for all its handy methods
type rwc struct {
io.WriteCloser
*bytes.Buffer
}
// these are explicit to override the *bytes.Buffer's methods
func (r *rwc) Write(b []byte) (int, error) { return r.WriteCloser.Write(b) }
func (r *rwc) Close() error { return r.WriteCloser.Close() }
// implements passthrough Write & closure call in Close
type fCloser struct { type fCloser struct {
close func() error close func() error
} }
@@ -74,6 +66,12 @@ type fCloser struct {
func (f *fCloser) Write(b []byte) (int, error) { return len(b), nil } func (f *fCloser) Write(b []byte) (int, error) { return len(b), nil }
func (f *fCloser) Close() error { return f.close() } func (f *fCloser) Close() error { return f.close() }
type nopCloser struct {
io.Writer
}
func (n *nopCloser) Close() error { return nil }
// multiWriteCloser returns the first write or close that returns a non-nil // multiWriteCloser returns the first write or close that returns a non-nil
// err, if no non-nil err is returned, then the returned bytes written will be // err, if no non-nil err is returned, then the returned bytes written will be
// from the last call to write. // from the last call to write.
@@ -102,17 +100,11 @@ func (m multiWriteCloser) Close() (err error) {
// logWriter will log (to real stderr) every call to Write as a line. it should // logWriter will log (to real stderr) every call to Write as a line. it should
// be wrapped with a lineWriter so that the output makes sense. // be wrapped with a lineWriter so that the output makes sense.
type logWriter struct { type logWriter struct {
ctx context.Context logrus.FieldLogger
appName string
path string
image string
reqID string
} }
func (l *logWriter) Write(b []byte) (int, error) { func (l *logWriter) Write(b []byte) (int, error) {
log := common.Logger(l.ctx) l.Debug(string(b))
log = log.WithFields(logrus.Fields{"user_log": true, "app_name": l.appName, "path": l.path, "image": l.image, "call_id": l.reqID})
log.Debug(string(b))
return len(b), nil return len(b), nil
} }
@@ -171,37 +163,14 @@ func (li *lineWriter) Close() error {
return err return err
} }
// dbWriter accumulates all calls to Write into an in memory buffer // io.Writer that allows limiting bytes written to w
// and writes them to the database when Close is called, returning
// any error from Close. it should be wrapped in a limitWriter to
// prevent blowing out the buffer and bloating the db.
type dbWriter struct {
*bytes.Buffer
db models.LogStore
ctx context.Context
reqID string
appName string
}
func (w *dbWriter) Close() error {
span, ctx := opentracing.StartSpanFromContext(context.Background(), "agent_log_write")
defer span.Finish()
return w.db.InsertLog(ctx, w.appName, w.reqID, w.String())
}
func (w *dbWriter) Write(b []byte) (int, error) {
return w.Buffer.Write(b)
}
// overrides Write, keeps Close
type limitWriter struct { type limitWriter struct {
n, max int n, max int
io.WriteCloser io.Writer
} }
func newLimitWriter(max int, w io.WriteCloser) io.WriteCloser { func newLimitWriter(max int, w io.Writer) io.Writer {
return &limitWriter{max: max, WriteCloser: w} return &limitWriter{max: max, Writer: w}
} }
func (l *limitWriter) Write(b []byte) (int, error) { func (l *limitWriter) Write(b []byte) (int, error) {
@@ -212,11 +181,11 @@ func (l *limitWriter) Write(b []byte) (int, error) {
// cut off to prevent gigantic line attack // cut off to prevent gigantic line attack
b = b[:l.max-l.n] b = b[:l.max-l.n]
} }
n, err := l.WriteCloser.Write(b) n, err := l.Writer.Write(b)
l.n += n l.n += n
if l.n >= l.max { if l.n >= l.max {
// write in truncation message to log once // write in truncation message to log once
l.WriteCloser.Write([]byte(fmt.Sprintf("\n-----max log size %d bytes exceeded, truncating log-----\n"))) l.Writer.Write([]byte(fmt.Sprintf("\n-----max log size %d bytes exceeded, truncating log-----\n")))
} }
return n, err return n, err
} }

View File

@@ -2,6 +2,7 @@ package datastoreutil
import ( import (
"context" "context"
"io"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@@ -100,7 +101,7 @@ func (m *metricds) GetCalls(ctx context.Context, filter *models.CallFilter) ([]*
return m.ds.GetCalls(ctx, filter) return m.ds.GetCalls(ctx, filter)
} }
func (m *metricds) InsertLog(ctx context.Context, appName, callID, callLog string) error { func (m *metricds) InsertLog(ctx context.Context, appName, callID string, callLog io.Reader) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "ds_insert_log") span, ctx := opentracing.StartSpanFromContext(ctx, "ds_insert_log")
defer span.Finish() defer span.Finish()
return m.ds.InsertLog(ctx, appName, callID, callLog) return m.ds.InsertLog(ctx, appName, callID, callLog)

View File

@@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@@ -600,9 +601,21 @@ func (ds *sqlStore) GetCalls(ctx context.Context, filter *models.CallFilter) ([]
return res, nil return res, nil
} }
func (ds *sqlStore) InsertLog(ctx context.Context, appName, callID, callLog string) error { func (ds *sqlStore) InsertLog(ctx context.Context, appName, callID string, logR io.Reader) error {
// coerce this into a string for sql
var log string
if stringer, ok := logR.(fmt.Stringer); ok {
log = stringer.String()
} else {
// TODO we could optimize for Size / buffer pool, but atm we aren't hitting
// this code path anyway (a fallback)
var b bytes.Buffer
io.Copy(&b, logR)
log = b.String()
}
query := ds.db.Rebind(`INSERT INTO logs (id, app_name, log) VALUES (?, ?, ?);`) query := ds.db.Rebind(`INSERT INTO logs (id, app_name, log) VALUES (?, ?, ?);`)
_, err := ds.db.ExecContext(ctx, query, callID, appName, callLog) _, err := ds.db.ExecContext(ctx, query, callID, appName, log)
return err return err
} }

View File

@@ -1,7 +1,10 @@
package logs package logs
import ( import (
"bytes"
"context" "context"
"io"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -27,8 +30,10 @@ func (m *mock) SetDatastore(ctx context.Context, ds models.Datastore) {
m.ds = ds m.ds = ds
} }
func (m *mock) InsertLog(ctx context.Context, appName, callID, callLog string) error { func (m *mock) InsertLog(ctx context.Context, appName, callID string, callLog io.Reader) error {
m.Logs[callID] = &models.CallLog{CallID: callID, Log: callLog} var b bytes.Buffer
io.Copy(&b, callLog)
m.Logs[callID] = &models.CallLog{CallID: callID, Log: b.String()}
return nil return nil
} }

View File

@@ -44,7 +44,8 @@ func Test(t *testing.T, fnl models.LogStore, ds models.Datastore) {
if err != nil { if err != nil {
t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err) t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err)
} }
err = fnl.InsertLog(ctx, call.AppName, call.ID, "test") log := strings.NewReader("test")
err = fnl.InsertLog(ctx, call.AppName, call.ID, log)
if err != nil { if err != nil {
t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err) t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err)
} }
@@ -52,11 +53,12 @@ func Test(t *testing.T, fnl models.LogStore, ds models.Datastore) {
t.Run("call-log-insert-get", func(t *testing.T) { t.Run("call-log-insert-get", func(t *testing.T) {
call.ID = id.New().String() call.ID = id.New().String()
err := ds.InsertCall(ctx, call) err := ds.InsertCall(ctx, call)
logText := "test"
if err != nil { if err != nil {
t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err) t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err)
} }
err = fnl.InsertLog(ctx, call.AppName, call.ID, logText) logText := "test"
log := strings.NewReader(logText)
err = fnl.InsertLog(ctx, call.AppName, call.ID, log)
if err != nil { if err != nil {
t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err) t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err)
} }
@@ -69,11 +71,12 @@ func Test(t *testing.T, fnl models.LogStore, ds models.Datastore) {
t.Run("call-log-insert-get-delete", func(t *testing.T) { t.Run("call-log-insert-get-delete", func(t *testing.T) {
call.ID = id.New().String() call.ID = id.New().String()
err := ds.InsertCall(ctx, call) err := ds.InsertCall(ctx, call)
logText := "test"
if err != nil { if err != nil {
t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err) t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err)
} }
err = fnl.InsertLog(ctx, call.AppName, call.ID, logText) logText := "test"
log := strings.NewReader(logText)
err = fnl.InsertLog(ctx, call.AppName, call.ID, log)
if err != nil { if err != nil {
t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err) t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err)
} }

View File

@@ -2,19 +2,17 @@ package models
import ( import (
"context" "context"
"io"
) )
type LogStore interface { type LogStore interface {
// TODO TODO TODO BAD BUG BUG BUG WILL ROBINSON
// TODO these need to take an app name or users can provide ids for
// other users calls with their own app name and access their logs.
// InsertLog will insert the log at callID, overwriting if it previously // InsertLog will insert the log at callID, overwriting if it previously
// existed. // existed.
InsertLog(ctx context.Context, appName, callID string, callLog string) error InsertLog(ctx context.Context, appName, callID string, callLog io.Reader) error
// GetLog will return the log at callID, an error will be returned if the log // GetLog will return the log at callID, an error will be returned if the log
// cannot be found. // cannot be found.
// TODO it would be nice if this were an io.Reader...
GetLog(ctx context.Context, appName, callID string) (*CallLog, error) GetLog(ctx context.Context, appName, callID string) (*CallLog, error)
// DeleteLog will remove the log at callID, it will not return an error if // DeleteLog will remove the log at callID, it will not return an error if