mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
add logrotate support for MinIO logs (#19641)
This commit is contained in:
parent
dbfb5e797b
commit
8c1bba681b
@ -65,5 +65,5 @@ var (
|
||||
MinioBannerName = "MinIO Object Storage Server"
|
||||
|
||||
// MinioLicense - MinIO server license.
|
||||
MinioLicense = "GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>"
|
||||
MinioLicense = "GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@ -20,6 +20,7 @@ package cmd
|
||||
import (
|
||||
"container/ring"
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@ -49,10 +50,10 @@ type HTTPConsoleLoggerSys struct {
|
||||
|
||||
// NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to
|
||||
// the console logging pub sub system
|
||||
func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys {
|
||||
func NewConsoleLogger(ctx context.Context, w io.Writer) *HTTPConsoleLoggerSys {
|
||||
return &HTTPConsoleLoggerSys{
|
||||
pubsub: pubsub.New[log.Info, madmin.LogMask](8),
|
||||
console: console.New(),
|
||||
console: console.New(w),
|
||||
logBuf: ring.New(defaultLogBufferCount),
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,6 @@ func newApp(name string) *cli.App {
|
||||
|
||||
// Register all commands.
|
||||
registerCommand(serverCmd)
|
||||
registerCommand(gatewayCmd) // hidden kept for guiding users.
|
||||
|
||||
// Set up app.
|
||||
cli.HelpFlag = cli.BoolFlag{
|
||||
@ -181,7 +180,7 @@ func versionBanner(c *cli.Context) io.Reader {
|
||||
banner := &strings.Builder{}
|
||||
fmt.Fprintln(banner, color.Bold("%s version %s (commit-id=%s)", c.App.Name, c.App.Version, CommitID))
|
||||
fmt.Fprintln(banner, color.Blue("Runtime:")+color.Bold(" %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH))
|
||||
fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>"))
|
||||
fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"))
|
||||
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear))
|
||||
return strings.NewReader(banner.String())
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -191,20 +192,19 @@ var ServerFlags = []cli.Flag{
|
||||
EnvVar: "MINIO_RECV_BUF_SIZE",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
var gatewayCmd = cli.Command{
|
||||
Name: "gateway",
|
||||
Usage: "start object storage gateway",
|
||||
Hidden: true,
|
||||
Flags: append(ServerFlags, GlobalFlags...),
|
||||
HideHelpCommand: true,
|
||||
Action: gatewayMain,
|
||||
}
|
||||
|
||||
func gatewayMain(ctx *cli.Context) error {
|
||||
logger.Fatal(errInvalidArgument, "Gateway is deprecated, To continue to use Gateway please use releases no later than 'RELEASE.2022-10-24T18-35-07Z'. We recommend all our users to migrate from gateway mode to server mode. Please read https://blog.min.io/deprecation-of-the-minio-gateway/")
|
||||
return nil
|
||||
cli.StringFlag{
|
||||
Name: "log-dir",
|
||||
Usage: "specify the directory to save the server log",
|
||||
EnvVar: "MINIO_LOG_DIR",
|
||||
Hidden: true,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "log-size",
|
||||
Usage: "specify the maximum server log file size in bytes before its rotated",
|
||||
Value: 10 * humanize.MiByte,
|
||||
EnvVar: "MINIO_LOG_SIZE",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
var serverCmd = cli.Command{
|
||||
@ -667,6 +667,29 @@ func getServerListenAddrs() []string {
|
||||
return addrs.ToSlice()
|
||||
}
|
||||
|
||||
var globalLoggerOutput io.WriteCloser
|
||||
|
||||
func initializeLogRotate(ctx *cli.Context) (io.WriteCloser, error) {
|
||||
lgDir := ctx.String("log-dir")
|
||||
if lgDir == "" {
|
||||
return os.Stderr, nil
|
||||
}
|
||||
lgDirAbs, err := filepath.Abs(lgDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lgSize := ctx.Int("log-size")
|
||||
output, err := logger.NewDir(logger.Options{
|
||||
Directory: lgDirAbs,
|
||||
MaximumFileSize: int64(lgSize),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.EnableJSON()
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// serverMain handler called for 'minio server' command.
|
||||
func serverMain(ctx *cli.Context) {
|
||||
var warnings []string
|
||||
@ -679,11 +702,23 @@ func serverMain(ctx *cli.Context) {
|
||||
|
||||
// Initialize globalConsoleSys system
|
||||
bootstrapTrace("newConsoleLogger", func() {
|
||||
globalConsoleSys = NewConsoleLogger(GlobalContext)
|
||||
output, err := initializeLogRotate(ctx)
|
||||
if err == nil {
|
||||
logger.Output = output
|
||||
globalConsoleSys = NewConsoleLogger(GlobalContext, output)
|
||||
globalLoggerOutput = output
|
||||
} else {
|
||||
logger.Output = os.Stderr
|
||||
globalConsoleSys = NewConsoleLogger(GlobalContext, os.Stderr)
|
||||
}
|
||||
logger.AddSystemTarget(GlobalContext, globalConsoleSys)
|
||||
|
||||
// Set node name, only set for distributed setup.
|
||||
globalConsoleSys.SetNodeName(globalLocalNodeName)
|
||||
if err != nil {
|
||||
// We can only log here since we need globalConsoleSys initialized
|
||||
logger.Fatal(err, "invalid --logrorate-dir option")
|
||||
}
|
||||
})
|
||||
|
||||
// Always load ENV variables from files first.
|
||||
|
@ -31,6 +31,10 @@ import (
|
||||
func handleSignals() {
|
||||
// Custom exit function
|
||||
exit := func(success bool) {
|
||||
if globalLoggerOutput != nil {
|
||||
globalLoggerOutput.Close()
|
||||
}
|
||||
|
||||
// If global profiler is set stop before we exit.
|
||||
globalProfilerMu.Lock()
|
||||
defer globalProfilerMu.Unlock()
|
||||
|
@ -109,7 +109,7 @@ func TestMain(m *testing.M) {
|
||||
setMaxResources(nil)
|
||||
|
||||
// Initialize globalConsoleSys system
|
||||
globalConsoleSys = NewConsoleLogger(context.Background())
|
||||
globalConsoleSys = NewConsoleLogger(context.Background(), io.Discard)
|
||||
|
||||
globalInternodeTransport = NewInternodeHTTPTransport(0)()
|
||||
|
||||
|
@ -149,4 +149,12 @@ var (
|
||||
}
|
||||
return fmt.Sprintf
|
||||
}()
|
||||
|
||||
TurnOff = func() {
|
||||
color.NoColor = true
|
||||
}
|
||||
|
||||
TurnOn = func() {
|
||||
color.NoColor = false
|
||||
}
|
||||
)
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/color"
|
||||
c "github.com/minio/pkg/v2/console"
|
||||
"github.com/minio/pkg/v2/logger/message/log"
|
||||
)
|
||||
|
||||
@ -99,8 +98,7 @@ func (f fatalMsg) json(msg string, args ...interface{}) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(logJSON))
|
||||
|
||||
fmt.Fprintln(Output, string(logJSON))
|
||||
ExitFunc(1)
|
||||
}
|
||||
|
||||
@ -139,16 +137,16 @@ func (f fatalMsg) pretty(msg string, args ...interface{}) {
|
||||
ansiSaveAttributes()
|
||||
// Print banner with or without the log tag
|
||||
if !tagPrinted {
|
||||
c.Print(logBanner)
|
||||
fmt.Fprint(Output, logBanner)
|
||||
tagPrinted = true
|
||||
} else {
|
||||
c.Print(emptyBanner)
|
||||
fmt.Fprint(Output, emptyBanner)
|
||||
}
|
||||
// Restore the text color of the error message
|
||||
ansiRestoreAttributes()
|
||||
ansiMoveRight(bannerWidth)
|
||||
// Continue error message printing
|
||||
c.Println(line)
|
||||
fmt.Fprintln(Output, line)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -176,7 +174,7 @@ func (i infoMsg) json(msg string, args ...interface{}) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(logJSON))
|
||||
fmt.Fprintln(Output, string(logJSON))
|
||||
}
|
||||
|
||||
func (i infoMsg) quiet(msg string, args ...interface{}) {
|
||||
@ -184,9 +182,9 @@ func (i infoMsg) quiet(msg string, args ...interface{}) {
|
||||
|
||||
func (i infoMsg) pretty(msg string, args ...interface{}) {
|
||||
if msg == "" {
|
||||
c.Println(args...)
|
||||
fmt.Fprintln(Output, args...)
|
||||
}
|
||||
c.Printf(msg, args...)
|
||||
fmt.Fprintf(Output, msg, args...)
|
||||
}
|
||||
|
||||
type errorMsg struct{}
|
||||
@ -209,7 +207,7 @@ func (i errorMsg) json(msg string, args ...interface{}) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(logJSON))
|
||||
fmt.Fprintln(Output, string(logJSON))
|
||||
}
|
||||
|
||||
func (i errorMsg) quiet(msg string, args ...interface{}) {
|
||||
@ -218,9 +216,9 @@ func (i errorMsg) quiet(msg string, args ...interface{}) {
|
||||
|
||||
func (i errorMsg) pretty(msg string, args ...interface{}) {
|
||||
if msg == "" {
|
||||
c.Println(args...)
|
||||
fmt.Fprintln(Output, args...)
|
||||
}
|
||||
c.Printf(msg, args...)
|
||||
fmt.Fprintf(Output, msg, args...)
|
||||
}
|
||||
|
||||
// Error :
|
||||
|
@ -23,6 +23,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@ -32,6 +34,7 @@ import (
|
||||
|
||||
"github.com/minio/highwayhash"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/color"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/pkg/v2/logger/message/log"
|
||||
)
|
||||
@ -49,8 +52,12 @@ const (
|
||||
InfoKind = madmin.LogKindInfo
|
||||
)
|
||||
|
||||
// DisableErrorLog avoids printing error/event/info kind of logs
|
||||
var DisableErrorLog = false
|
||||
var (
|
||||
// DisableErrorLog avoids printing error/event/info kind of logs
|
||||
DisableErrorLog = false
|
||||
// Output allows configuring custom writer, defaults to os.Stderr
|
||||
Output io.Writer = os.Stderr
|
||||
)
|
||||
|
||||
var trimStrings []string
|
||||
|
||||
@ -78,6 +85,7 @@ func EnableQuiet() {
|
||||
|
||||
// EnableJSON - outputs logs in json format.
|
||||
func EnableJSON() {
|
||||
color.TurnOff() // no colored outputs necessary in JSON mode.
|
||||
jsonFlag = true
|
||||
quietFlag = true
|
||||
}
|
||||
|
161
internal/logger/logrotate.go
Normal file
161
internal/logger/logrotate.go
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
)
|
||||
|
||||
func defaultFilenameFunc() string {
|
||||
return fmt.Sprintf("minio-%s.log", fmt.Sprintf("%X", time.Now().UTC().UnixNano()))
|
||||
}
|
||||
|
||||
// Options define configuration options for Writer
|
||||
type Options struct {
|
||||
// Directory defines the directory where log files will be written to.
|
||||
// If the directory does not exist, it will be created.
|
||||
Directory string
|
||||
|
||||
// MaximumFileSize defines the maximum size of each log file in bytes.
|
||||
MaximumFileSize int64
|
||||
|
||||
// FileNameFunc specifies the name a new file will take.
|
||||
// FileNameFunc must ensure collisions in filenames do not occur.
|
||||
// Do not rely on timestamps to be unique, high throughput writes
|
||||
// may fall on the same timestamp.
|
||||
// Eg.
|
||||
// 2020-03-28_15-00-945-<random-hash>.log
|
||||
// When FileNameFunc is not specified, DefaultFilenameFunc will be used.
|
||||
FileNameFunc func() string
|
||||
}
|
||||
|
||||
// Writer is a concurrency-safe writer with file rotation.
|
||||
type Writer struct {
|
||||
// opts are the configuration options for this Writer
|
||||
opts Options
|
||||
|
||||
// f is the currently open file used for appends.
|
||||
// Writes to f are only synchronized once Close() is called,
|
||||
// or when files are being rotated.
|
||||
f *os.File
|
||||
|
||||
pw *xioutil.PipeWriter
|
||||
pr *xioutil.PipeReader
|
||||
}
|
||||
|
||||
// Write writes p into the current file, rotating if necessary.
|
||||
// Write is non-blocking, if the writer's queue is not full.
|
||||
// Write is blocking otherwise.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
return w.pw.Write(p)
|
||||
}
|
||||
|
||||
// Close closes the writer.
|
||||
// Any accepted writes will be flushed. Any new writes will be rejected.
|
||||
// Once Close() exits, files are synchronized to disk.
|
||||
func (w *Writer) Close() error {
|
||||
w.pw.CloseWithError(nil)
|
||||
|
||||
if w.f != nil {
|
||||
if err := w.closeCurrentFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) listen() {
|
||||
for {
|
||||
var r io.Reader = w.pr
|
||||
if w.opts.MaximumFileSize > 0 {
|
||||
r = io.LimitReader(w.pr, w.opts.MaximumFileSize)
|
||||
}
|
||||
if _, err := io.Copy(w.f, r); err != nil {
|
||||
fmt.Println("Failed to write to log file", err)
|
||||
}
|
||||
if err := w.rotate(); err != nil {
|
||||
fmt.Println("Failed to rotate log file", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) closeCurrentFile() error {
|
||||
if err := w.f.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close current log file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) rotate() error {
|
||||
if w.f != nil {
|
||||
if err := w.closeCurrentFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
path := filepath.Join(w.opts.Directory, w.opts.FileNameFunc())
|
||||
f, err := newFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new file at %v: %w", path, err)
|
||||
}
|
||||
|
||||
w.f = f
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDir creates a new concurrency safe Writer which performs log rotation.
|
||||
func NewDir(opts Options) (io.WriteCloser, error) {
|
||||
if err := os.MkdirAll(opts.Directory, os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("directory %v does not exist and could not be created: %w", opts.Directory, err)
|
||||
}
|
||||
|
||||
if opts.FileNameFunc == nil {
|
||||
opts.FileNameFunc = defaultFilenameFunc
|
||||
}
|
||||
|
||||
pr, pw := xioutil.WaitPipe()
|
||||
|
||||
w := &Writer{
|
||||
opts: opts,
|
||||
pw: pw,
|
||||
pr: pr,
|
||||
}
|
||||
|
||||
if w.f == nil {
|
||||
if err := w.rotate(); err != nil {
|
||||
return nil, fmt.Errorf("Failed to create log file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
go w.listen()
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func newFile(path string) (*os.File, error) {
|
||||
return os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE|os.O_SYNC, 0o666)
|
||||
}
|
@ -20,18 +20,20 @@ package console
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
"github.com/minio/pkg/v2/logger/message/log"
|
||||
)
|
||||
|
||||
// Target implements loggerTarget to send log
|
||||
// in plain or json format to the standard output.
|
||||
type Target struct{}
|
||||
type Target struct {
|
||||
output io.Writer
|
||||
}
|
||||
|
||||
// Validate - validate if the tty can be written to
|
||||
func (c *Target) Validate() error {
|
||||
@ -58,12 +60,12 @@ func (c *Target) Send(e interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(logJSON))
|
||||
fmt.Fprintln(c.output, string(logJSON))
|
||||
return nil
|
||||
}
|
||||
|
||||
if entry.Level == logger.EventKind {
|
||||
fmt.Println(entry.Message)
|
||||
fmt.Fprintln(c.output, entry.Message)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -146,13 +148,13 @@ func (c *Target) Send(e interface{}) error {
|
||||
apiString, timeString, deploymentID, requestID, remoteHost, host, userAgent,
|
||||
msg, tagString, strings.Join(trace, "\n"))
|
||||
|
||||
console.Println(output)
|
||||
fmt.Fprintln(c.output, output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// New initializes a new logger target
|
||||
// which prints log directly in the standard
|
||||
// output.
|
||||
func New() *Target {
|
||||
return &Target{}
|
||||
func New(w io.Writer) *Target {
|
||||
return &Target{output: w}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user