프로그래밍/Go

Go Cron - robfig/cron/v3

동네로봇 2023. 1. 28. 16:09

https://github.com/robfig/cron

 

GitHub - robfig/cron: a cron library for go

a cron library for go. Contribute to robfig/cron development by creating an account on GitHub.

github.com

 

Cron 오픈소스 라이브러리를 사용하여 특정 시간마다 동작하는 기능을 구현한다. 

 

Download

go get github.com/robfig/cron/v3@v3.0.0

 

Import

import "github.com/robfig/cron/v3"

 

사용법

사용법은 간단하다. 내가 사용하고자 하는 시간 스케줄링과 기능을 설정해주고 Start 해주면 된다.

func main() {
    c := cron.New()

    c.AddFunc("* * * * *", func() {
        log.Println("Hello, World!")
    })

    c.Start()

    time.Sleep(5*time.Minute)

    c.Stop()
}

 

 

cron.New()로 생성되는 기본 parser는 Cron wikipedia 페이지에 설명된 표준을 준수한다. 따라서 실행 시간은 기본 Cron Spec 같다. 위에서 설정한 기능은 매 분마다 Hello, World!를 찍는다. 

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

 

몇 초마다 실행시키고 싶다거나, 혹은 내가 원하는 시간 스펙대로 설정을 변경하고 싶은 경우에 New()에 옵션을 주어 설정을 변경할 수도 있다.

// Seconds field, required
cron.New(cron.WithSeconds())

// Seconds field, optional
cron.New(cron.WithParser(cron.NewParser(
    cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)))

 

 

cron expression 외에도 미리 정의된 여러 예약어도 사용할 수 있다. 

func main() {
    c := cron.New()

    c.AddFunc("* * * * *", func() {
        log.Println("Hello, World!")
    })
    
    // Run once a day at midnight
    c.AddFunc("@daily", func() {
        log.Println("Hello, World!")
    })
    
    // Run once an hour at the beginning of the hour
    c.AddFunc("@hourly", func() {
        log.Println("Hello, World!")
    })
    
    // activates after 1 hour, 30 minutes, 10 seconds, 
    // and then every interval after that.
    c.AddFunc("@every 1h30m10s", func() {
        log.Println("Hello, World!")
    })

    c.Start()

    /*
    Wait....
    */

    c.Stop()
}

 

 

만약 매 시간마다 동작하는 기능에서 panic이 발생하면 어떻게 될까? 설정해놓은 모든 cronjob이 종료되고 서비스도 종료되는 것일까?

이런 문제를 방지하기 위해 panic 발생 시 Recover 기능을 제공한다. 

 

 

cron.New(cron.WithChain(
  cron.Recover(logger),  // or use cron.DefaultLogger
))

 

cron에서 제공하는 기본 logger가 아니라, 내가 정의한 별도의 logger를 사용하고 싶다면 역시 logger 설정을 통해 변경도 가능하다.

func main() {
    logPath := "/Users/cron/log"
    if err := os.MkdirAll(logPath, 0666); err != nil {
        log.Fatalln("failed to make log", err)
    }

    date := time.Now().Format("2006-01-02")
    fileName := fmt.Sprintf("%v.%v.log", "crontest", date)

    logFilePath := path.Join(logPath, fileName)
    fpLog, _ := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    
    cron.New(cron.WithLogger(cron.VerbosePrintfLogger(log.New(fpLog, "", log.LstdFlags|log.Lshortfile))))
    
    // ...
}

 

 

테스트 코드

custom logger를 설정하여 매 분마다 발생하는 panic을 recovery해 로그로 남기는 테스트 코드.

package test

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"log"
	"os"
	"os/signal"
	"path"
	"syscall"
	"testing"
	"time"
)

func TestCron(t *testing.T) {

	logPath := "/Users/cron/log"
	if err := os.MkdirAll(logPath, 0666); err != nil {
		log.Fatalln("failed to make log", err)
	}

	date := time.Now().Format("2006-01-02")
	fileName := fmt.Sprintf("%v.%v.log", "crontest", date)

	logFilePath := path.Join(logPath, fileName)
	fpLog, _ := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)

	done := make(chan bool, 1)
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		for {
			select {
			case s := <-sig:
				log.Println("Get Signal :", s)
				done <- true
			}
		}
	}()

	c := cron.New(cron.WithChain(cron.Recover(cron.VerbosePrintfLogger(log.New(fpLog, "", log.LstdFlags|log.Lshortfile)))))

	c.AddFunc("* * * * *", func() {
		panic("panic")
	})

	log.Println("Start Cron.")
	c.Start()

	for {
		select {
		case <-done:
			c.Stop()
			log.Println("Cron Done.")
			return
		}
	}
}