diff --git a/.gitignore b/.gitignore index 849fb6afa82a2afefe61f1fbbbee47d800941c7c..cdf3587bb50088283fbc05c8c363741c902e2be8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.idea /bin /pkg +/logs media-repo.yaml diff --git a/config.sample.yaml b/config.sample.yaml index 460952fb3c779568845c214baf8f8bb459554a1b..a6c7e46ece9488daa0b3b6f000914f9085d329a3 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -2,6 +2,7 @@ repo: bindAddress: '127.0.0.1' port: 8000 + logDirectory: logs # The configuration for the homeservers this media repository is known to control. Servers # not listed here will not be able to upload media. diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/download.go b/src/github.com/turt2live/matrix-media-repo/client/r0/download.go index 7bf606ce83b1da7831bcc4a8d78ad40f1db0d4a4..928cc315b991e7450fb89bd974ea4b2880fa43f1 100644 --- a/src/github.com/turt2live/matrix-media-repo/client/r0/download.go +++ b/src/github.com/turt2live/matrix-media-repo/client/r0/download.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/client" "github.com/turt2live/matrix-media-repo/config" "github.com/turt2live/matrix-media-repo/media_handler" @@ -26,7 +27,7 @@ type DownloadMediaResponse struct { Location string } -func DownloadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c config.MediaRepoConfig) interface{} { +func DownloadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c config.MediaRepoConfig, log *logrus.Entry) interface{} { if !ValidateUserCanDownload(r, db, c) { return client.AuthFailed() } @@ -37,14 +38,21 @@ func DownloadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, mediaId := params["mediaId"] filename := params["filename"] - media, err := media_handler.FindMedia(r.Context(), server, mediaId, c, db) + log = log.WithFields(logrus.Fields{ + "mediaId": mediaId, + "server": server, + "filename": filename, + }) + + media, err := media_handler.FindMedia(r.Context(), server, mediaId, c, db, log) if err != nil { if err == media_handler.ErrMediaNotFound { return client.NotFoundError() } else if err == media_handler.ErrMediaTooLarge { return client.RequestTooLarge() } - return client.InternalServerError(err.Error()) + log.Error("Unexpected error locating media: " + err.Error()) + return client.InternalServerError("Unexpected Error") } if filename == "" { diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go b/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go index 8e57a1048dd89d17c5dcf5ad6fed5e7f786e531b..b1830d255af2d312f78a96b47183a90774ad567d 100644 --- a/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go +++ b/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/gorilla/mux" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/client" "github.com/turt2live/matrix-media-repo/config" "github.com/turt2live/matrix-media-repo/media_handler" @@ -22,7 +23,7 @@ import ( // Headers: Content-Type // Body: <byte[]> -func ThumbnailMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c config.MediaRepoConfig) interface{} { +func ThumbnailMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c config.MediaRepoConfig, log *logrus.Entry) interface{} { if !ValidateUserCanDownload(r, db, c) { return client.AuthFailed() } @@ -32,6 +33,11 @@ func ThumbnailMedia(w http.ResponseWriter, r *http.Request, db storage.Database, server := params["server"] mediaId := params["mediaId"] + log = log.WithFields(logrus.Fields{ + "mediaId": mediaId, + "server": server, + }) + widthStr := r.URL.Query().Get("width") heightStr := r.URL.Query().Get("height") method := r.URL.Query().Get("method") @@ -42,14 +48,14 @@ func ThumbnailMedia(w http.ResponseWriter, r *http.Request, db storage.Database, if widthStr != "" { parsedWidth, err := strconv.Atoi(widthStr) if err != nil { - return client.InternalServerError(err.Error()) + return client.InternalServerError("Width does not appear to be an integer") } width = parsedWidth } if heightStr != "" { parsedHeight, err := strconv.Atoi(heightStr) if err != nil { - return client.InternalServerError(err.Error()) + return client.InternalServerError("Height does not appear to be an integer") } height = parsedHeight } @@ -57,17 +63,27 @@ func ThumbnailMedia(w http.ResponseWriter, r *http.Request, db storage.Database, method = "crop" } - media, err := media_handler.FindMedia(r.Context(), server, mediaId, c, db) + log = log.WithFields(logrus.Fields{ + "requestedWidth": width, + "requestedHeight": height, + "requestedMethod": method, + }) + + media, err := media_handler.FindMedia(r.Context(), server, mediaId, c, db, log) if err != nil { if err == media_handler.ErrMediaNotFound { return client.NotFoundError() + } else if err == media_handler.ErrMediaTooLarge { + return client.RequestTooLarge() } - return client.InternalServerError(err.Error()) + log.Error("Unexpected error locating media: " + err.Error()) + return client.InternalServerError("Unexpected Error") } - thumb, err := media_handler.GetThumbnail(r.Context(), media, width, height, method, c, db) + thumb, err := media_handler.GetThumbnail(r.Context(), media, width, height, method, c, db, log) if err != nil { - return client.InternalServerError(err.Error()) + log.Error("Unexpected error getting thumbnail: " + err.Error()) + return client.InternalServerError("Unexpected Error") } return &DownloadMediaResponse{ diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go b/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go index 6b8d09887b26c25fc4d37949881d4ff18caaa557..34904c2fc3fb30d3f0b8aafce385d7924928950d 100644 --- a/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go +++ b/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go @@ -4,6 +4,7 @@ import ( "io" "net/http" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/client" "github.com/turt2live/matrix-media-repo/config" "github.com/turt2live/matrix-media-repo/media_handler" @@ -23,7 +24,7 @@ type MediaUploadedResponse struct { ContentUri string `json:"content_uri"` } -func UploadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c config.MediaRepoConfig) interface{} { +func UploadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c config.MediaRepoConfig, log *logrus.Entry) interface{} { accessToken := util.GetAccessTokenFromRequest(r) userId, err := util.GetUserIdFromToken(r.Context(), r.Host, accessToken, c) if err != nil || userId == "" { @@ -35,6 +36,11 @@ func UploadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c filename = "upload.bin" } + log = log.WithFields(logrus.Fields{ + "filename": filename, + "userId": userId, + }) + contentType := r.Header.Get("Content-Type") if contentType == "" { contentType = "application/octet-stream" // binary @@ -54,9 +60,10 @@ func UploadMedia(w http.ResponseWriter, r *http.Request, db storage.Database, c Contents: reader, } - mxc, err := request.StoreAndGetMxcUri(r.Context(), c, db) + mxc, err := request.StoreAndGetMxcUri(r.Context(), c, db, log) if err != nil { - return client.InternalServerError(err.Error()) + log.Error("Unexpected error storing media: " + err.Error()) + return client.InternalServerError("Unexpected Error") } return &MediaUploadedResponse{mxc} diff --git a/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go b/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go index 2f6ea627fd61ca4795c5b54cb90272e94dbe9595..f5d2fe4ce9b612f4184fb2dcedb4522353e2600b 100644 --- a/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go +++ b/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go @@ -1,31 +1,39 @@ package main import ( - json "encoding/json" + "encoding/json" "fmt" "io" "net/http" "os" + "reflect" "strconv" "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/client" "github.com/turt2live/matrix-media-repo/client/r0" "github.com/turt2live/matrix-media-repo/config" + "github.com/turt2live/matrix-media-repo/logging" "github.com/turt2live/matrix-media-repo/storage" "github.com/turt2live/matrix-media-repo/util" ) const UnkErrJson = `{"code":"M_UNKNOWN","message":"Unexpected error processing response"}` +type requestCounter struct { + lastId int +} + type Handler struct { - h func(http.ResponseWriter, *http.Request, storage.Database, config.MediaRepoConfig) interface{} + h func(http.ResponseWriter, *http.Request, storage.Database, config.MediaRepoConfig, *log.Entry) interface{} opts HandlerOpts } type HandlerOpts struct { db storage.Database config config.MediaRepoConfig + reqCounter *requestCounter } type EmptyResponse struct {} @@ -38,24 +46,34 @@ func main() { panic(err) } + err = logging.Setup(c.General.LogDirectory) + if err != nil { + panic(err) + } + + log.Info("Starting media repository...") + db, err := storage.OpenDatabase(c.Database.Postgres) if err != nil { panic(err) } - hOpts := HandlerOpts{*db, c} + counter := requestCounter{} + hOpts := HandlerOpts{*db, c, &counter} uploadHandler := Handler{r0.UploadMedia, hOpts} downloadHandler := Handler{r0.DownloadMedia, hOpts} thumbnailHandler := Handler{r0.ThumbnailMedia, hOpts} // r0 endpoints + log.Info("Registering r0 endpoints") rtr.Handle("/_matrix/media/r0/upload", uploadHandler).Methods("POST") rtr.Handle("/_matrix/media/r0/download/{server:[a-zA-Z0-9.:-_]+}/{mediaId:[a-zA-Z0-9]+}", downloadHandler).Methods("GET") rtr.Handle("/_matrix/media/r0/download/{server:[a-zA-Z0-9.:-_]+}/{mediaId:[a-zA-Z0-9]+}/{filename:[a-zA-Z0-9._-]+}", downloadHandler).Methods("GET") rtr.Handle("/_matrix/media/r0/thumbnail/{server:[a-zA-Z0-9.:-_]+}/{mediaId:[a-zA-Z0-9]+}", thumbnailHandler).Methods("GET") // v1 endpoints (legacy) + log.Info("Registering v1 endpoints") rtr.Handle("/_matrix/media/v1/upload", uploadHandler).Methods("POST") rtr.Handle("/_matrix/media/v1/download/{server:[a-zA-Z0-9.:-_]+}/{mediaId:[a-zA-Z0-9]+}", downloadHandler).Methods("GET") rtr.Handle("/_matrix/media/v1/download/{server:[a-zA-Z0-9.:-_]+}/{mediaId:[a-zA-Z0-9]+}/{filename:[a-zA-Z0-9._-]+}", downloadHandler).Methods("GET") @@ -64,11 +82,25 @@ func main() { // TODO: Intercept 404, 500, and 400 to respond with M_NOT_FOUND and M_UNKNOWN // TODO: Rate limiting (429 M_LIMIT_EXCEEDED) + address:=c.General.BindAddress+":"+strconv.Itoa(c.General.Port) http.Handle("/", rtr) - http.ListenAndServe(c.General.BindAddress+":"+strconv.Itoa(c.General.Port), nil) + + log.WithField("address", address).Info("Started up. Listening at http://" + address) + http.ListenAndServe(address, nil) } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + contextLog := log.WithFields(log.Fields{ + "method": r.Method, + "host": r.Host, + "resource": r.URL.Path, + "contentType": r.Header.Get("Content-Type"), + "contentLength": r.Header.Get("Content-Length"), + "queryString": util.GetLogSafeQueryString(r), + "requestId": h.opts.reqCounter.GetNextId(), + }) + contextLog.Info("Received request") + // Send CORS and other basic headers w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") @@ -80,7 +112,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Process response var res interface{} = client.AuthFailed() if util.IsServerOurs(r.Host, h.opts.config) { - res = h.h(w, r, h.opts.db, h.opts.config) + contextLog.Info("Server is owned by us, processing request") + res = h.h(w, r, h.opts.db, h.opts.config, contextLog) if res == nil { res = &EmptyResponse{} } @@ -94,6 +127,13 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } jsonStr := string(b) + _, isDownload := res.(r0.DownloadMediaResponse) + if isDownload { + contextLog.Info("Replying with result: " + reflect.TypeOf(res).Elem().Name()) + } else { + contextLog.Info("Replying with result: " + jsonStr) + } + switch result := res.(type) { case *client.ErrorResponse: w.Header().Set("Content-Type", "application/json") @@ -131,4 +171,11 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { io.WriteString(w, jsonStr) break } +} + +func (c *requestCounter) GetNextId() string { + strId := strconv.Itoa(c.lastId) + c.lastId = c.lastId + 1 + + return "REQ-" + strId } \ No newline at end of file diff --git a/src/github.com/turt2live/matrix-media-repo/config/config.go b/src/github.com/turt2live/matrix-media-repo/config/config.go index df902f2a632500da1d27392c6d1a03f9cd5dea71..9037d51d7334d3a67dbc21301b01627804324d79 100644 --- a/src/github.com/turt2live/matrix-media-repo/config/config.go +++ b/src/github.com/turt2live/matrix-media-repo/config/config.go @@ -17,6 +17,7 @@ type MediaRepoConfig struct { General struct { BindAddress string `yaml:"bindAddress"` Port int `yaml:"port"` + LogDirectory string `yaml:"logDirectory"` } `yaml:"repo"` Homeservers []HomeserverConfig `yaml:"homeservers,flow"` diff --git a/src/github.com/turt2live/matrix-media-repo/logging/logger.go b/src/github.com/turt2live/matrix-media-repo/logging/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..ca434f4e9517b4c756b6de851123d26074e8deae --- /dev/null +++ b/src/github.com/turt2live/matrix-media-repo/logging/logger.go @@ -0,0 +1,58 @@ +package logging + +import ( + "os" + "path" + "time" + + "github.com/lestrrat/go-file-rotatelogs" + "github.com/rifflock/lfshook" + "github.com/sirupsen/logrus" +) + +type utcFormatter struct { + logrus.Formatter +} + +func (f utcFormatter) Format(entry *logrus.Entry) ([]byte, error) { + entry.Time = entry.Time.UTC() + return f.Formatter.Format(entry) +} + +func Setup(dir string) error { + logrus.SetFormatter(&utcFormatter{ + &logrus.TextFormatter{ + TimestampFormat: "2006-01-02 15:04:05.000 Z07:00", + FullTimestamp: true, + DisableColors: false, + DisableTimestamp: false, + QuoteEmptyFields: true, + }, + }) + logrus.SetOutput(os.Stdout) + + if dir == "" { return nil } + _ = os.MkdirAll(dir, os.ModePerm) + + logFile := path.Join(dir, "media_repo.log") + writer, err := rotatelogs.New( + logFile + ".%Y%m%d%H%M", + rotatelogs.WithLinkName(logFile), + rotatelogs.WithMaxAge((24 * time.Hour) * 14), // keep for 14 days + rotatelogs.WithRotationTime(24 * time.Hour), // rotate every 24 hours + ) + if err != nil { + return err + } + + logrus.AddHook(lfshook.NewHook(lfshook.WriterMap{ + logrus.DebugLevel: writer, + logrus.InfoLevel: writer, + logrus.WarnLevel: writer, + logrus.ErrorLevel: writer, + logrus.FatalLevel: writer, + logrus.PanicLevel: writer, + })) + + return nil +} \ No newline at end of file diff --git a/src/github.com/turt2live/matrix-media-repo/media_handler/locator.go b/src/github.com/turt2live/matrix-media-repo/media_handler/locator.go index bc447262158610cba72dffccbf55d1ff11e5c218..3bfff80c97bee85f819a73f5983e9c3a6c0a3267 100644 --- a/src/github.com/turt2live/matrix-media-repo/media_handler/locator.go +++ b/src/github.com/turt2live/matrix-media-repo/media_handler/locator.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/matrix-org/gomatrixserverlib" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/config" "github.com/turt2live/matrix-media-repo/storage" "github.com/turt2live/matrix-media-repo/types" @@ -17,15 +18,18 @@ import ( var ErrMediaNotFound = errors.New("media not found") var ErrMediaTooLarge = errors.New("media too large") -func FindMedia(ctx context.Context, server string, mediaId string, c config.MediaRepoConfig, db storage.Database) (types.Media, error) { +func FindMedia(ctx context.Context, server string, mediaId string, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (types.Media, error) { + log.Info("Looking up media") media, err := db.GetMedia(ctx, server, mediaId) if err != nil { if err == sql.ErrNoRows { if util.IsServerOurs(server, c) { + log.Warn("Media not found") return media, ErrMediaNotFound } - media, err = DownloadMedia(ctx, server, mediaId, c, db) + log.Info("Attempting to download remote media") + media, err = DownloadMedia(ctx, server, mediaId, c, db, log) return media, err } return media, err @@ -34,7 +38,7 @@ func FindMedia(ctx context.Context, server string, mediaId string, c config.Medi return media, nil } -func DownloadMedia(ctx context.Context, server string, mediaId string, c config.MediaRepoConfig, db storage.Database) (types.Media, error) { +func DownloadMedia(ctx context.Context, server string, mediaId string, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (types.Media, error) { request := &MediaUploadRequest{ UploadedBy: "", Host: server, @@ -51,8 +55,10 @@ func DownloadMedia(ctx context.Context, server string, mediaId string, c config. } if resp.StatusCode == 404 { + log.Info("Remote media not found") return types.Media{}, ErrMediaNotFound } else if resp.StatusCode != 200 { + log.Info("Unknown error fetching remote media; received status code " + strconv.Itoa(resp.StatusCode)) return types.Media{}, errors.New("could not fetch remote media") } @@ -62,6 +68,7 @@ func DownloadMedia(ctx context.Context, server string, mediaId string, c config. return types.Media{}, err } if c.Downloads.MaxSizeBytes > 0 && contentLength > c.Downloads.MaxSizeBytes { + log.Warn("Attempted to download media that was too large") return types.Media{}, ErrMediaTooLarge } @@ -73,5 +80,6 @@ func DownloadMedia(ctx context.Context, server string, mediaId string, c config. request.DesiredFilename = params["filename"] } - return request.StoreMediaWithId(ctx, mediaId, c, db) -} + log.Info("Persisting downloaded remote media") + return request.StoreMediaWithId(ctx, mediaId, c, db, log) +} \ No newline at end of file diff --git a/src/github.com/turt2live/matrix-media-repo/media_handler/media_handler.go b/src/github.com/turt2live/matrix-media-repo/media_handler/media_handler.go index 150d9b130f081f01a023bbd18abb7c38ae1d1983..baa97c0be6ed26b59bb79eeffc2dc30c45667b11 100644 --- a/src/github.com/turt2live/matrix-media-repo/media_handler/media_handler.go +++ b/src/github.com/turt2live/matrix-media-repo/media_handler/media_handler.go @@ -6,6 +6,7 @@ import ( "os" "time" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/config" "github.com/turt2live/matrix-media-repo/storage" "github.com/turt2live/matrix-media-repo/types" @@ -20,8 +21,8 @@ type MediaUploadRequest struct { ContentType string } -func (r MediaUploadRequest) StoreAndGetMxcUri(ctx context.Context, c config.MediaRepoConfig, db storage.Database) (string, error) { - media, err := r.StoreMedia(ctx, c, db) +func (r MediaUploadRequest) StoreAndGetMxcUri(ctx context.Context, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (string, error) { + media, err := r.StoreMedia(ctx, c, db, log) if err != nil { return "", err } @@ -29,7 +30,11 @@ func (r MediaUploadRequest) StoreAndGetMxcUri(ctx context.Context, c config.Medi return util.MediaToMxc(&media), nil } -func (r MediaUploadRequest) StoreMediaWithId(ctx context.Context, mediaId string, c config.MediaRepoConfig, db storage.Database) (types.Media, error) { +func (r MediaUploadRequest) StoreMediaWithId(ctx context.Context, mediaId string, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (types.Media, error) { + log = log.WithFields(logrus.Fields{ + "handlerMediaId": mediaId, + }) + destination, err := storage.PersistFile(ctx, r.Contents, c, db) if err != nil { return types.Media{}, err @@ -58,16 +63,20 @@ func (r MediaUploadRequest) StoreMediaWithId(ctx context.Context, mediaId string // If the media is exactly the same, just return it if IsMediaSame(media, r) { + log.Info("Exact media duplicate found, returning unaltered media record") return media, nil } if media.Origin == r.Host { + log.Info("Media duplicate found, assigning a new media ID for new origin") // Generate a new ID for this upload media.MediaId = GenerateMediaId() break } } + log.Info("Duplicate media found, generating new record using existing file") + media.Origin = r.Host media.UserId = r.UploadedBy media.UploadName = r.DesiredFilename @@ -82,6 +91,8 @@ func (r MediaUploadRequest) StoreMediaWithId(ctx context.Context, mediaId string return media, nil } + log.Info("Persisting unique media record") + fileSize, err := util.FileSize(destination) if err != nil { return types.Media{}, err @@ -108,8 +119,8 @@ func (r MediaUploadRequest) StoreMediaWithId(ctx context.Context, mediaId string return *media, nil } -func (r MediaUploadRequest) StoreMedia(ctx context.Context, c config.MediaRepoConfig, db storage.Database) (types.Media, error) { - return r.StoreMediaWithId(ctx, GenerateMediaId(), c, db) +func (r MediaUploadRequest) StoreMedia(ctx context.Context, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (types.Media, error) { + return r.StoreMediaWithId(ctx, GenerateMediaId(), c, db, log) } func GenerateMediaId() string { diff --git a/src/github.com/turt2live/matrix-media-repo/media_handler/thumbnailer.go b/src/github.com/turt2live/matrix-media-repo/media_handler/thumbnailer.go index f000e8c7b20153bb0a96dd23ea937646b21bbb6b..a93a6a1652644675c6a0f2d1f2380f4f3178bd92 100644 --- a/src/github.com/turt2live/matrix-media-repo/media_handler/thumbnailer.go +++ b/src/github.com/turt2live/matrix-media-repo/media_handler/thumbnailer.go @@ -8,13 +8,14 @@ import ( "time" "github.com/disintegration/imaging" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/config" "github.com/turt2live/matrix-media-repo/storage" "github.com/turt2live/matrix-media-repo/types" "github.com/turt2live/matrix-media-repo/util" ) -func GetThumbnail(ctx context.Context, media types.Media, width int, height int, method string, c config.MediaRepoConfig, db storage.Database) (types.Thumbnail, error) { +func GetThumbnail(ctx context.Context, media types.Media, width int, height int, method string, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (types.Thumbnail, error) { if width <= 0 { return types.Thumbnail{}, errors.New("width must be positive") } @@ -58,22 +59,32 @@ func GetThumbnail(ctx context.Context, media types.Media, width int, height int, } } + log = log.WithFields(logrus.Fields{ + "targetWidth": targetWidth, + "targetHeight": targetHeight, + }) + log.Info("Looking up thumbnail") + thumb, err := db.GetThumbnail(ctx, media.Origin, media.MediaId, targetWidth, targetHeight, method) if err != nil && err != sql.ErrNoRows { + log.Error("Unexpected error processing thumbnail lookup: " + err.Error()) return thumb, err } if err != sql.ErrNoRows { - return thumb, err + log.Info("Found existing thumbnail") + return thumb, nil } if media.SizeBytes > c.Thumbnails.MaxSourceBytes { + log.Warn("Media too large to thumbnail") return thumb, errors.New("cannot thumbnail, image too large") } - return generateThumbnail(ctx, media, targetWidth, targetHeight, method, c, db) + log.Info("Generating new thumbnail") + return generateThumbnail(ctx, media, targetWidth, targetHeight, method, c, db, log) } -func generateThumbnail(ctx context.Context, media types.Media, width int, height int, method string, c config.MediaRepoConfig, db storage.Database) (types.Thumbnail, error) { +func generateThumbnail(ctx context.Context, media types.Media, width int, height int, method string, c config.MediaRepoConfig, db storage.Database, log *logrus.Entry) (types.Thumbnail, error) { thumb := &types.Thumbnail{ Origin: media.Origin, MediaId: media.MediaId, @@ -99,6 +110,7 @@ func generateThumbnail(ctx context.Context, media types.Media, width int, height if aspectRatio == targetAspectRatio { // Highly unlikely, but if the aspect ratios match then just resize method = "scale" + log.Info("Aspect ratio is the same, converting method to 'scale'") } if srcWidth <= width && srcHeight <= height { @@ -106,6 +118,7 @@ func generateThumbnail(ctx context.Context, media types.Media, width int, height thumb.ContentType = media.ContentType thumb.Location = media.Location thumb.SizeBytes = media.SizeBytes + log.Warn("Image too small, returning raw image") return *thumb, nil } @@ -114,6 +127,7 @@ func generateThumbnail(ctx context.Context, media types.Media, width int, height } else if method == "crop" { src = imaging.Fill(src, width, height, imaging.Center, imaging.Lanczos) } else { + log.Error("Unrecognized thumbnail method: " + method) return *thumb, errors.New("unrecognized method: " + method) } @@ -121,17 +135,20 @@ func generateThumbnail(ctx context.Context, media types.Media, width int, height imgData := &bytes.Buffer{} err = imaging.Encode(imgData, src, imaging.PNG) if err != nil { + log.Error("Unexpected error encoding thumbnail: " + err.Error()) return *thumb, err } // Reset the buffer pointer and store the file location, err := storage.PersistFile(ctx, imgData, c, db) if err != nil { + log.Error("Unexpected error saving thumbnail: " + err.Error()) return *thumb, err } fileSize, err := util.FileSize(location) if err != nil { + log.Error("Unexpected error getting the size of the thumbnail: " + err.Error()) return *thumb, err } @@ -141,6 +158,7 @@ func generateThumbnail(ctx context.Context, media types.Media, width int, height err = db.InsertThumbnail(ctx, thumb) if err != nil { + log.Error("Unexpected error caching thumbnail: " + err.Error()) return *thumb, err } diff --git a/src/github.com/turt2live/matrix-media-repo/util/http.go b/src/github.com/turt2live/matrix-media-repo/util/http.go index 1161672b26884631db863ccee773760f562a0457..1dbedeee61df1abc72a9bb16eca298ba51f9b292 100644 --- a/src/github.com/turt2live/matrix-media-repo/util/http.go +++ b/src/github.com/turt2live/matrix-media-repo/util/http.go @@ -12,4 +12,14 @@ func GetAccessTokenFromRequest(request *http.Request) (string) { } return request.URL.Query().Get("access_token") -} \ No newline at end of file +} + +func GetLogSafeQueryString(r *http.Request) (string) { + qs := r.URL.Query() + + if qs.Get("access_token") != "" { + qs.Set("access_token", "redacted") + } + + return qs.Encode() +} diff --git a/vendor/manifest b/vendor/manifest index 22c63c01cbf61f9142a7eb1de6f075f66d89aec6..29ba77e3d7b3dd2faa8c4fe921dca5b0ddae5ee8 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -13,6 +13,18 @@ "revision": "2d5fef06b891c971b14aa6f71ca5ab6c03a36e0e", "branch": "master" }, + { + "importpath": "github.com/lestrrat/go-file-rotatelogs", + "repository": "https://github.com/lestrrat/go-file-rotatelogs", + "revision": "9df8b44f21785240553882138c5df2e9cc1db910", + "branch": "master" + }, + { + "importpath": "github.com/lestrrat/go-strftime", + "repository": "https://github.com/lestrrat/go-strftime", + "revision": "04ef93e285313c8978cbc7cad26d2aa7a9927451", + "branch": "master" + }, { "importpath": "github.com/lib/pq", "repository": "https://github.com/lib/pq", @@ -37,6 +49,18 @@ "revision": "8b1c8ab81986c1ce7f06a52fce48f4a1156b66ee", "branch": "master" }, + { + "importpath": "github.com/pkg/errors", + "repository": "https://github.com/pkg/errors", + "revision": "f15c970de5b76fac0b59abb32d62c17cc7bed265", + "branch": "master" + }, + { + "importpath": "github.com/rifflock/lfshook", + "repository": "https://github.com/rifflock/lfshook", + "revision": "3bcf86f879c771238f8a67832a1af71308801a47", + "branch": "master" + }, { "importpath": "github.com/sirupsen/logrus", "repository": "https://github.com/sirupsen/logrus",