Преглед на файлове

Merge pull request #12362 from ahmetalpbalkan/logs/since

Add --since argument to docker logs cmd
Sebastiaan van Stijn преди 10 години
родител
ревизия
d0459abe6e

+ 2 - 16
api/client/events.go

@@ -2,8 +2,6 @@ package client
 
 
 import (
 import (
 	"net/url"
 	"net/url"
-	"strconv"
-	"time"
 
 
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
@@ -26,7 +24,6 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
 
 
 	var (
 	var (
 		v               = url.Values{}
 		v               = url.Values{}
-		loc             = time.FixedZone(time.Now().Zone())
 		eventFilterArgs = filters.Args{}
 		eventFilterArgs = filters.Args{}
 	)
 	)
 
 
@@ -39,22 +36,11 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
 			return err
 			return err
 		}
 		}
 	}
 	}
-	var setTime = func(key, value string) {
-		format := timeutils.RFC3339NanoFixed
-		if len(value) < len(format) {
-			format = format[:len(value)]
-		}
-		if t, err := time.ParseInLocation(format, value, loc); err == nil {
-			v.Set(key, strconv.FormatInt(t.Unix(), 10))
-		} else {
-			v.Set(key, value)
-		}
-	}
 	if *since != "" {
 	if *since != "" {
-		setTime("since", *since)
+		v.Set("since", timeutils.GetTimestamp(*since))
 	}
 	}
 	if *until != "" {
 	if *until != "" {
-		setTime("until", *until)
+		v.Set("until", timeutils.GetTimestamp(*until))
 	}
 	}
 	if len(eventFilterArgs) > 0 {
 	if len(eventFilterArgs) > 0 {
 		filterJSON, err := filters.ToParam(eventFilterArgs)
 		filterJSON, err := filters.ToParam(eventFilterArgs)

+ 6 - 0
api/client/logs.go

@@ -7,6 +7,7 @@ import (
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/timeutils"
 )
 )
 
 
 // CmdLogs fetches the logs of a given container.
 // CmdLogs fetches the logs of a given container.
@@ -16,6 +17,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 	var (
 	var (
 		cmd    = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
 		cmd    = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
 		follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
 		follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
+		since  = cmd.String([]string{"-since"}, "", "Show logs since timestamp")
 		times  = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
 		times  = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
 		tail   = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
 		tail   = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
 	)
 	)
@@ -43,6 +45,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 	v.Set("stdout", "1")
 	v.Set("stdout", "1")
 	v.Set("stderr", "1")
 	v.Set("stderr", "1")
 
 
+	if *since != "" {
+		v.Set("since", timeutils.GetTimestamp(*since))
+	}
+
 	if *times {
 	if *times {
 		v.Set("timestamps", "1")
 		v.Set("timestamps", "1")
 	}
 	}

+ 10 - 0
api/server/server.go

@@ -594,9 +594,19 @@ func (s *Server) getContainersLogs(version version.Version, w http.ResponseWrite
 		return fmt.Errorf("Bad parameters: you must choose at least one stream")
 		return fmt.Errorf("Bad parameters: you must choose at least one stream")
 	}
 	}
 
 
+	var since time.Time
+	if r.Form.Get("since") != "" {
+		s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
+		if err != nil {
+			return err
+		}
+		since = time.Unix(s, 0)
+	}
+
 	logsConfig := &daemon.ContainerLogsConfig{
 	logsConfig := &daemon.ContainerLogsConfig{
 		Follow:     boolValue(r, "follow"),
 		Follow:     boolValue(r, "follow"),
 		Timestamps: boolValue(r, "timestamps"),
 		Timestamps: boolValue(r, "timestamps"),
+		Since:      since,
 		Tail:       r.Form.Get("tail"),
 		Tail:       r.Form.Get("tail"),
 		UseStdout:  stdout,
 		UseStdout:  stdout,
 		UseStderr:  stderr,
 		UseStderr:  stderr,

+ 1 - 1
contrib/completion/bash/docker

@@ -593,7 +593,7 @@ _docker_logs() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--follow -f --help --tail --timestamps -t" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--follow -f --help --since --tail --timestamps -t" -- "$cur" ) )
 			;;
 			;;
 		*)
 		*)
 			local counter=$(__docker_pos_first_nonflag '--tail')
 			local counter=$(__docker_pos_first_nonflag '--tail')

+ 1 - 0
contrib/completion/fish/docker.fish

@@ -233,6 +233,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a logs -d 'Fetch the log
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s f -l follow -d 'Follow log output'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s f -l follow -d 'Follow log output'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s t -l timestamps -d 'Show timestamps'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s t -l timestamps -d 'Show timestamps'
+complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l since -d 'Show logs since timestamp'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l tail -d 'Output the specified number of lines at the end of logs (defaults to all logs)'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l tail -d 'Output the specified number of lines at the end of logs (defaults to all logs)'
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -a '(__fish_print_docker_containers running)' -d "Container"
 complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -a '(__fish_print_docker_containers running)' -d "Container"
 
 

+ 1 - 0
contrib/completion/zsh/_docker

@@ -305,6 +305,7 @@ __docker_subcommand () {
         (logs)
         (logs)
             _arguments \
             _arguments \
                 {-f,--follow}'[Follow log output]' \
                 {-f,--follow}'[Follow log output]' \
+                '-s,--since[Show logs since timestamp]' \
                 {-t,--timestamps}'[Show timestamps]' \
                 {-t,--timestamps}'[Show timestamps]' \
                 '--tail=-[Output the last K lines]:lines:(1 10 20 50 all)' \
                 '--tail=-[Output the last K lines]:lines:(1 10 20 50 all)' \
                 '*:containers:__docker_containers'
                 '*:containers:__docker_containers'

+ 10 - 3
daemon/logs.go

@@ -8,6 +8,7 @@ import (
 	"os"
 	"os"
 	"strconv"
 	"strconv"
 	"sync"
 	"sync"
+	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/pkg/jsonlog"
 	"github.com/docker/docker/pkg/jsonlog"
@@ -19,6 +20,7 @@ import (
 type ContainerLogsConfig struct {
 type ContainerLogsConfig struct {
 	Follow, Timestamps   bool
 	Follow, Timestamps   bool
 	Tail                 string
 	Tail                 string
+	Since                time.Time
 	UseStdout, UseStderr bool
 	UseStdout, UseStderr bool
 	OutStream            io.Writer
 	OutStream            io.Writer
 }
 }
@@ -88,6 +90,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
 				lines = -1
 				lines = -1
 			}
 			}
 		}
 		}
+
 		if lines != 0 {
 		if lines != 0 {
 			if lines > 0 {
 			if lines > 0 {
 				f := cLog.(*os.File)
 				f := cLog.(*os.File)
@@ -101,9 +104,11 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
 				}
 				}
 				cLog = tmp
 				cLog = tmp
 			}
 			}
+
 			dec := json.NewDecoder(cLog)
 			dec := json.NewDecoder(cLog)
 			l := &jsonlog.JSONLog{}
 			l := &jsonlog.JSONLog{}
 			for {
 			for {
+				l.Reset()
 				if err := dec.Decode(l); err == io.EOF {
 				if err := dec.Decode(l); err == io.EOF {
 					break
 					break
 				} else if err != nil {
 				} else if err != nil {
@@ -111,6 +116,9 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
 					break
 					break
 				}
 				}
 				logLine := l.Log
 				logLine := l.Log
+				if !config.Since.IsZero() && l.Created.Before(config.Since) {
+					continue
+				}
 				if config.Timestamps {
 				if config.Timestamps {
 					// format can be "" or time format, so here can't be error
 					// format can be "" or time format, so here can't be error
 					logLine, _ = l.Format(format)
 					logLine, _ = l.Format(format)
@@ -121,7 +129,6 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
 				if l.Stream == "stderr" && config.UseStderr {
 				if l.Stream == "stderr" && config.UseStderr {
 					io.WriteString(errStream, logLine)
 					io.WriteString(errStream, logLine)
 				}
 				}
-				l.Reset()
 			}
 			}
 		}
 		}
 	}
 	}
@@ -139,7 +146,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
 			stdoutPipe := container.StdoutLogPipe()
 			stdoutPipe := container.StdoutLogPipe()
 			defer stdoutPipe.Close()
 			defer stdoutPipe.Close()
 			go func() {
 			go func() {
-				errors <- jsonlog.WriteLog(stdoutPipe, outStream, format)
+				errors <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since)
 				wg.Done()
 				wg.Done()
 			}()
 			}()
 		}
 		}
@@ -148,7 +155,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
 			stderrPipe := container.StderrLogPipe()
 			stderrPipe := container.StderrLogPipe()
 			defer stderrPipe.Close()
 			defer stderrPipe.Close()
 			go func() {
 			go func() {
-				errors <- jsonlog.WriteLog(stderrPipe, errStream, format)
+				errors <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since)
 				wg.Done()
 				wg.Done()
 			}()
 			}()
 		}
 		}

+ 5 - 0
docs/man/docker-logs.1.md

@@ -8,6 +8,7 @@ docker-logs - Fetch the logs of a container
 **docker logs**
 **docker logs**
 [**-f**|**--follow**[=*false*]]
 [**-f**|**--follow**[=*false*]]
 [**--help**]
 [**--help**]
+[**--since**[=*SINCE*]]
 [**-t**|**--timestamps**[=*false*]]
 [**-t**|**--timestamps**[=*false*]]
 [**--tail**[=*"all"*]]
 [**--tail**[=*"all"*]]
 CONTAINER
 CONTAINER
@@ -31,6 +32,9 @@ then continue streaming new output from the container’s stdout and stderr.
 **-f**, **--follow**=*true*|*false*
 **-f**, **--follow**=*true*|*false*
    Follow log output. The default is *false*.
    Follow log output. The default is *false*.
 
 
+**--since**=""
+   Show logs since timestamp
+
 **-t**, **--timestamps**=*true*|*false*
 **-t**, **--timestamps**=*true*|*false*
    Show timestamps. The default is *false*.
    Show timestamps. The default is *false*.
 
 
@@ -42,3 +46,4 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
 based on docker.com source material and internal work.
 based on docker.com source material and internal work.
 June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
 June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
 July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
 July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
+April 2015, updated by Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>

+ 6 - 0
docs/sources/reference/api/docker_remote_api.md

@@ -52,6 +52,12 @@ You can still call an old version of the API using
 You can now supply a `stream` bool to get only one set of stats and
 You can now supply a `stream` bool to get only one set of stats and
 disconnect
 disconnect
 
 
+`GET /containers(id)/logs`
+
+**New!**
+
+This endpoint now accepts a `since` timestamp parameter.
+
 ## v1.18
 ## v1.18
 
 
 ### Full documentation
 ### Full documentation

+ 3 - 1
docs/sources/reference/api/docker_remote_api_v1.19.md

@@ -477,7 +477,7 @@ Get stdout and stderr logs from the container ``id``
 
 
 **Example request**:
 **Example request**:
 
 
-       GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1&timestamps=1&follow=1&tail=10 HTTP/1.1
+       GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1&timestamps=1&follow=1&tail=10&since=1428990821 HTTP/1.1
 
 
 **Example response**:
 **Example response**:
 
 
@@ -493,6 +493,8 @@ Query Parameters:
 -   **follow** – 1/True/true or 0/False/false, return stream. Default false
 -   **follow** – 1/True/true or 0/False/false, return stream. Default false
 -   **stdout** – 1/True/true or 0/False/false, show stdout log. Default false
 -   **stdout** – 1/True/true or 0/False/false, show stdout log. Default false
 -   **stderr** – 1/True/true or 0/False/false, show stderr log. Default false
 -   **stderr** – 1/True/true or 0/False/false, show stderr log. Default false
+-   **since** – UNIX timestamp (integer) to filter logs. Specifying a timestamp
+    will only output log-entries since that timestamp. Default: 0 (unfiltered)
 -   **timestamps** – 1/True/true or 0/False/false, print timestamps for
 -   **timestamps** – 1/True/true or 0/False/false, print timestamps for
         every log line. Default false
         every log line. Default false
 -   **tail** – Output specified number of lines at the end of logs: `all` or `<number>`. Default all
 -   **tail** – Output specified number of lines at the end of logs: `all` or `<number>`. Default all

+ 5 - 0
docs/sources/reference/commandline/cli.md

@@ -1632,6 +1632,7 @@ For example:
     Fetch the logs of a container
     Fetch the logs of a container
 
 
       -f, --follow=false        Follow log output
       -f, --follow=false        Follow log output
+      --since=""                Show logs since timestamp
       -t, --timestamps=false    Show timestamps
       -t, --timestamps=false    Show timestamps
       --tail="all"              Number of lines to show from the end of the logs
       --tail="all"              Number of lines to show from the end of the logs
 
 
@@ -1651,6 +1652,10 @@ timestamp, for example `2014-09-16T06:17:46.000000000Z`, to each
 log entry. To ensure that the timestamps for are aligned the
 log entry. To ensure that the timestamps for are aligned the
 nano-second part of the timestamp will be padded with zero when necessary.
 nano-second part of the timestamp will be padded with zero when necessary.
 
 
+The `--since` option shows logs of a container generated only after
+the given date, specified as RFC 3339 or UNIX timestamp. The `--since` option
+can be combined with the `--follow` and `--tail` options.
+
 ## pause
 ## pause
 
 
     Usage: docker pause CONTAINER [CONTAINER...]
     Usage: docker pause CONTAINER [CONTAINER...]

+ 75 - 0
integration-cli/docker_cli_logs_test.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"os/exec"
 	"os/exec"
 	"regexp"
 	"regexp"
+	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -276,6 +277,80 @@ func (s *DockerSuite) TestLogsFollowStopped(c *check.C) {
 	deleteContainer(cleanedContainerID)
 	deleteContainer(cleanedContainerID)
 }
 }
 
 
+func (s *DockerSuite) TestLogsSince(c *check.C) {
+	name := "testlogssince"
+	runCmd := exec.Command(dockerBinary, "run", "--name="+name, "busybox", "/bin/sh", "-c", `date +%s; for i in $(seq 1 5); do sleep 1; echo log$i; done`)
+	out, _, err := runCommandWithOutput(runCmd)
+	if err != nil {
+		c.Fatalf("run failed with errors: %s, %v", out, err)
+	}
+
+	outLines := strings.Split(out, "\n")
+	startUnix, _ := strconv.ParseInt(outLines[0], 10, 64)
+	since := startUnix + 3
+	logsCmd := exec.Command(dockerBinary, "logs", "-t", fmt.Sprintf("--since=%v", since), name)
+
+	out, _, err = runCommandWithOutput(logsCmd)
+	if err != nil {
+		c.Fatalf("failed to log container: %s, %v", out, err)
+	}
+
+	// Skip 2 seconds
+	unexpected := []string{"log1", "log2"}
+	for _, v := range unexpected {
+		if strings.Contains(out, v) {
+			c.Fatalf("unexpected log message returned=%v, since=%v\nout=%v", v, since, out)
+		}
+	}
+
+	// Test with default value specified and parameter omitted
+	expected := []string{"log1", "log2", "log3", "log4", "log5"}
+	for _, cmd := range []*exec.Cmd{
+		exec.Command(dockerBinary, "logs", "-t", name),
+		exec.Command(dockerBinary, "logs", "-t", "--since=0", name),
+	} {
+		out, _, err = runCommandWithOutput(cmd)
+		if err != nil {
+			c.Fatalf("failed to log container: %s, %v", out, err)
+		}
+		for _, v := range expected {
+			if !strings.Contains(out, v) {
+				c.Fatalf("'%v' does not contain=%v\nout=%s", cmd.Args, v, out)
+			}
+		}
+	}
+}
+
+func (s *DockerSuite) TestLogsSinceFutureFollow(c *check.C) {
+	runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `for i in $(seq 1 5); do date +%s; sleep 1; done`)
+	out, _, err := runCommandWithOutput(runCmd)
+	if err != nil {
+		c.Fatalf("run failed with errors: %s, %v", out, err)
+	}
+	cleanedContainerID := strings.TrimSpace(out)
+
+	now := daemonTime(c).Unix()
+	since := now + 2
+	logCmd := exec.Command(dockerBinary, "logs", "-f", fmt.Sprintf("--since=%v", since), cleanedContainerID)
+	out, _, err = runCommandWithOutput(logCmd)
+	if err != nil {
+		c.Fatalf("failed to log container: %s, %v", out, err)
+	}
+	lines := strings.Split(strings.TrimSpace(out), "\n")
+	if len(lines) == 0 {
+		c.Fatal("got no log lines")
+	}
+	for _, v := range lines {
+		ts, err := strconv.ParseInt(v, 10, 64)
+		if err != nil {
+			c.Fatalf("cannot parse timestamp output from log: '%v'\nout=%s", v, out)
+		}
+		if ts < since {
+			c.Fatalf("earlier log found. since=%v logdate=%v", since, ts)
+		}
+	}
+}
+
 // Regression test for #8832
 // Regression test for #8832
 func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) {
 func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) {
 	runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`)
 	runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`)

+ 5 - 2
pkg/jsonlog/jsonlog.go

@@ -32,16 +32,20 @@ func (jl *JSONLog) Reset() {
 	jl.Created = time.Time{}
 	jl.Created = time.Time{}
 }
 }
 
 
-func WriteLog(src io.Reader, dst io.Writer, format string) error {
+func WriteLog(src io.Reader, dst io.Writer, format string, since time.Time) error {
 	dec := json.NewDecoder(src)
 	dec := json.NewDecoder(src)
 	l := &JSONLog{}
 	l := &JSONLog{}
 	for {
 	for {
+		l.Reset()
 		if err := dec.Decode(l); err == io.EOF {
 		if err := dec.Decode(l); err == io.EOF {
 			return nil
 			return nil
 		} else if err != nil {
 		} else if err != nil {
 			logrus.Printf("Error streaming logs: %s", err)
 			logrus.Printf("Error streaming logs: %s", err)
 			return err
 			return err
 		}
 		}
+		if !since.IsZero() && l.Created.Before(since) {
+			continue
+		}
 		line, err := l.Format(format)
 		line, err := l.Format(format)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -49,6 +53,5 @@ func WriteLog(src io.Reader, dst io.Writer, format string) error {
 		if _, err := io.WriteString(dst, line); err != nil {
 		if _, err := io.WriteString(dst, line); err != nil {
 			return err
 			return err
 		}
 		}
-		l.Reset()
 	}
 	}
 }
 }

+ 2 - 2
pkg/jsonlog/jsonlog_test.go

@@ -21,7 +21,7 @@ func TestWriteLog(t *testing.T) {
 	}
 	}
 	w := bytes.NewBuffer(nil)
 	w := bytes.NewBuffer(nil)
 	format := timeutils.RFC3339NanoFixed
 	format := timeutils.RFC3339NanoFixed
-	if err := WriteLog(&buf, w, format); err != nil {
+	if err := WriteLog(&buf, w, format, time.Time{}); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	res := w.String()
 	res := w.String()
@@ -52,7 +52,7 @@ func BenchmarkWriteLog(b *testing.B) {
 	b.SetBytes(int64(r.Len()))
 	b.SetBytes(int64(r.Len()))
 	b.ResetTimer()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 	for i := 0; i < b.N; i++ {
-		if err := WriteLog(r, w, format); err != nil {
+		if err := WriteLog(r, w, format, time.Time{}); err != nil {
 			b.Fatal(err)
 			b.Fatal(err)
 		}
 		}
 		b.StopTimer()
 		b.StopTimer()

+ 22 - 0
pkg/timeutils/utils.go

@@ -0,0 +1,22 @@
+package timeutils
+
+import (
+	"strconv"
+	"time"
+)
+
+// GetTimestamp tries to parse given string as RFC3339 time
+// or Unix timestamp, if successful returns a Unix timestamp
+// as string otherwise returns value back.
+func GetTimestamp(value string) string {
+	format := RFC3339NanoFixed
+	loc := time.FixedZone(time.Now().Zone())
+	if len(value) < len(format) {
+		format = format[:len(value)]
+	}
+	t, err := time.ParseInLocation(format, value, loc)
+	if err != nil {
+		return value
+	}
+	return strconv.FormatInt(t.Unix(), 10)
+}