Skip to content

Commit

Permalink
Merge pull request #11 from tarosky/issue/10
Browse files Browse the repository at this point in the history
Support logrotate
Close #10.
  • Loading branch information
harai authored Jan 17, 2021
2 parents 713e245 + 939bb04 commit 5b7d88e
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 45 deletions.
186 changes: 153 additions & 33 deletions imgserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ type config struct {
permanentCache string
gracefulShutdownTimeout uint
port int
log *zap.Logger
logPath string
errorLogPath string
// log *zap.Logger
}

// Environment holds values needed to execute the entire program.
Expand All @@ -72,34 +74,110 @@ type environment struct {
log *zap.Logger
}

func createLogger() *zap.Logger {
config := &zap.Config{
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
Development: true,
Encoding: "json",
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: zapcore.OmitKey,
CallerKey: zapcore.OmitKey,
FunctionKey: zapcore.OmitKey,
MessageKey: "message",
StacktraceKey: zapcore.OmitKey,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
},
// This implements zapcore.WriteSyncer interface.
type lockedFileWriteSyncer struct {
m sync.Mutex
f *os.File
path string
}

func newLockedFileWriteSyncer(path string) *lockedFileWriteSyncer {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
fmt.Fprintf(os.Stderr, "error while creating log file: path: %s", err.Error())
panic(err)
}

return &lockedFileWriteSyncer{
f: f,
path: path,
}
log, err := config.Build(zap.WithCaller(false))
}

func (s *lockedFileWriteSyncer) Write(bs []byte) (int, error) {
s.m.Lock()
defer s.m.Unlock()

return s.f.Write(bs)
}

func (s *lockedFileWriteSyncer) Sync() error {
s.m.Lock()
defer s.m.Unlock()

return s.f.Sync()
}

func (s *lockedFileWriteSyncer) reopen() {
s.m.Lock()
defer s.m.Unlock()

if err := s.f.Close(); err != nil {
fmt.Fprintf(
os.Stderr, "error while reopening file: path: %s, err: %s", s.path, err.Error())
}

f, err := os.OpenFile(s.path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
panic("failed to initialize logger")
fmt.Fprintf(
os.Stderr, "error while reopening file: path: %s, err: %s", s.path, err.Error())
panic(err)
}

return log
s.f = f
}

func (s *lockedFileWriteSyncer) Close() error {
s.m.Lock()
defer s.m.Unlock()

return s.f.Close()
}

func createLogger(ctx context.Context, logPath, errorLogPath string) *zap.Logger {
enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: zapcore.OmitKey,
CallerKey: zapcore.OmitKey,
FunctionKey: zapcore.OmitKey,
MessageKey: "message",
StacktraceKey: zapcore.OmitKey,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
})

out := newLockedFileWriteSyncer(logPath)
errOut := newLockedFileWriteSyncer(errorLogPath)

sigusr1Ch := make(chan os.Signal)
signal.Notify(sigusr1Ch, syscall.SIGUSR1)

go func() {
for {
select {
case _, ok := <-sigusr1Ch:
if !ok {
break
}
out.reopen()
errOut.reopen()
case <-ctx.Done():
signal.Stop(sigusr1Ch)
close(sigusr1Ch)
break
}
}
}()

return zap.New(
zapcore.NewCore(enc, out, zap.NewAtomicLevelAt(zap.DebugLevel)),
zap.ErrorOutput(errOut),
zap.Development(),
zap.WithCaller(false))
}

func main() {
Expand Down Expand Up @@ -162,11 +240,36 @@ func main() {
Aliases: []string{"p"},
Value: 8080,
},
&cli.StringFlag{
Name: "log-path",
Aliases: []string{"l"},
Required: true,
},
&cli.StringFlag{
Name: "error-log-path",
Aliases: []string{"el"},
Required: true,
},
}

app.Action = func(c *cli.Context) error {
log := createLogger()
defer log.Sync()
efsMouthPath, err := filepath.Abs(c.String("efs-mount-path"))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get efs-mount-path: %s", err.Error())
panic(err)
}

logPath, err := filepath.Abs(c.String("log-path"))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get log-path: %s", err.Error())
panic(err)
}

errorLogPath, err := filepath.Abs(c.String("error-log-path"))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get error-log-path: %s", err.Error())
panic(err)
}

cfg := &config{
region: c.String("region"),
Expand All @@ -175,22 +278,39 @@ func main() {
s3DestKeyBase: c.String("s3-dest-key-base"),
sqsQueueURL: c.String("sqs-queue-url"),
sqsBatchWaitTime: c.Uint("sqs-batch-wait-time"),
efsMountPath: c.String("efs-mount-path"),
efsMountPath: efsMouthPath,
temporaryCache: fmt.Sprintf("public, max-age=%d", c.Uint("temp-resp-max-age")),
permanentCache: fmt.Sprintf("public, max-age=%d", c.Uint("perm-resp-max-age")),
gracefulShutdownTimeout: c.Uint("graceful-shutdown-timeout"),
port: c.Int("port"),
log: createLogger(),
logPath: logPath,
errorLogPath: errorLogPath,
}
log := createLogger(c.Context, cfg.logPath, cfg.errorLogPath)
defer log.Sync()

gin.SetMode(gin.ReleaseMode)
e := newEnvironment(cfg)
e := newEnvironment(cfg, log)
e.run(c.Context, e.runServer)

return nil
}

err := app.Run(os.Args)
ctx, cancel := context.WithCancel(context.Background())

sigCh := make(chan os.Signal)
signal.Notify(sigCh, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
defer func() {
signal.Stop(sigCh)
close(sigCh)
}()

<-sigCh
cancel()
}()

err := app.RunContext(ctx, os.Args)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -265,14 +385,14 @@ func createAWSSession(cfg *config) *session.Session {
return session.Must(session.NewSession(c))
}

func newEnvironment(cfg *config) *environment {
func newEnvironment(cfg *config, log *zap.Logger) *environment {
awsSession := createAWSSession(cfg)
return &environment{
config: *cfg,
awsSession: awsSession,
s3Client: s3.New(awsSession),
sqsClient: sqs.New(awsSession),
log: cfg.log,
log: log,
}
}

Expand Down
35 changes: 35 additions & 0 deletions imgserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"syscall"
"testing"
"time"

Expand Down Expand Up @@ -458,3 +460,36 @@ func (s *ImgServerSuite) Test_Accepted_NoS3_EFS_BatchSendWait() {
s.Assert().Len(s.receiveSQSMessages(ctx), 5)
})
}

func (s *ImgServerSuite) Test_ReopenLogFile() {
oldLogPath := s.env.efsMountPath + "/imgserver.log.old"
currentLogPath := s.env.efsMountPath + "/imgserver.log"

s.env.log.Info("first message")

s.Require().NoError(os.Rename(currentLogPath, oldLogPath))

s.env.log.Info("second message")

p, err := os.FindProcess(os.Getpid())
s.Require().NoError(err)
s.Require().NoError(p.Signal(syscall.SIGUSR1)) // Reopen log files

time.Sleep(time.Second)

s.env.log.Info("third message")

oldBytes, err := ioutil.ReadFile(oldLogPath)
s.Require().NoError(err)

currentBytes, err := ioutil.ReadFile(currentLogPath)
s.Require().NoError(err)

oldLog := string(oldBytes)
currentLog := string(currentBytes)

s.Assert().Contains(oldLog, "first")
s.Assert().Contains(oldLog, "second")

s.Assert().Contains(currentLog, "third")
}
29 changes: 17 additions & 12 deletions test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func getTestConfig(name string) *config {
sqsName)
efsPath := fmt.Sprintf("work/test/%s/%s", name, generateSafeRandomString())

return &config{
cfg := &config{
region: region,
accessKeyID: readTestConfig("access-key-id"),
secretAccessKey: readTestConfig("secret-access-key"),
Expand All @@ -102,8 +102,11 @@ func getTestConfig(name string) *config {
permanentCache: fmt.Sprintf("public, max-age=%d", 365*24*60*60),
gracefulShutdownTimeout: 5,
port: 0, // Not used
log: createLogger(),
logPath: efsPath + "/imgserver.log",
errorLogPath: efsPath + "/imgserver-error.log",
}

return cfg
}

func getTestSQSQueueNameFromURL(url string) string {
Expand All @@ -112,25 +115,27 @@ func getTestSQSQueueNameFromURL(url string) string {
}

func newTestEnvironment(name string, s *TestSuite) *environment {
e := newEnvironment(getTestConfig(name))

sqsName := getTestSQSQueueNameFromURL(e.sqsQueueURL)

_, err := e.sqsClient.CreateQueueWithContext(s.ctx, &sqs.CreateQueueInput{
QueueName: &sqsName,
})
require.NoError(s.T(), err, "failed to create SQS queue")
cfg := getTestConfig(name)

require.NoError(
s.T(),
os.RemoveAll(e.efsMountPath),
os.RemoveAll(cfg.efsMountPath),
"failed to remove directory")

require.NoError(
s.T(),
os.MkdirAll(e.efsMountPath, 0755),
os.MkdirAll(cfg.efsMountPath, 0755),
"failed to create directory")

log := createLogger(s.ctx, cfg.logPath, cfg.errorLogPath)
e := newEnvironment(cfg, log)

sqsName := getTestSQSQueueNameFromURL(e.sqsQueueURL)
_, err := e.sqsClient.CreateQueueWithContext(s.ctx, &sqs.CreateQueueInput{
QueueName: &sqsName,
})
require.NoError(s.T(), err, "failed to create SQS queue")

return e
}

Expand Down

0 comments on commit 5b7d88e

Please sign in to comment.