add support for compressibility of log file#29932
Conversation
There was a problem hiding this comment.
I wonder this should be a string (e.g. "gzip") for extensibility
There was a problem hiding this comment.
Let's use AtomicFileWriter
https://github.com/docker/docker/blob/master/pkg/ioutils/fswriters.go
There was a problem hiding this comment.
@AkihiroSuda The data source is an io.Reader interface type, so I think using io.Copy might be a bit more appropriate here.
There was a problem hiding this comment.
Please add a comment, UT, and doc?
|
Design SGTM |
|
Thanks @AkihiroSuda for review, I will refactor the code according to your comments. |
ee9745d to
2fcbb08
Compare
|
In general, the actual json-log log files are designed for internal use by docker, and not for external consumption. The rotated files are actually used when running TL;DR, I'm not sure we should implement this /cc @cpuguy83 |
|
I don't really see it as harmful. We may just want to compress by default (on rotation) and not even provide a configuration for it. |
ce83e84 to
b42c1df
Compare
|
@thaJeztah @cpuguy83 @AkihiroSuda , Thank so much for your advice and guidance. My test as follow: step 2: Check the log files list after about one minute step 3: Extract the first timestamp from the oldest log file ( step 4: run step 5: Interrupt the |
|
The design looks good. Let's review. |
daemon/logger/jsonfilelog/read.go
Outdated
There was a problem hiding this comment.
This kinda looks dangerous. You might read too much into memory.
There was a problem hiding this comment.
We can replace this with a temporary file, but it may reduce efficiency.
There was a problem hiding this comment.
I think we have to, or implement a seeker for a compressed stream.
There was a problem hiding this comment.
this should be io.Copy instead of read everything in memory
There was a problem hiding this comment.
Do we need to support a bunch of formats?
There was a problem hiding this comment.
Not relying on the archive package would definitely be a good thing.
There was a problem hiding this comment.
I agree with you, but @AkihiroSuda has different opinions:
I wonder this should be a string (e.g. "gzip") for extensibility
There was a problem hiding this comment.
I would like to not have a flag and just compress... do you see why people would not want this?
I guess there's people violating the rule of everything in /var/lib/docker should be private to the engine.
There was a problem hiding this comment.
But they should be punished.
There was a problem hiding this comment.
After discussing with others, we decided to not compress by default in this driver as it could break people who aren't expecting it.
There was a problem hiding this comment.
I don't think we want to read this in here. This should stream to the compressed writer.
|
@cpuguy83 Thanks a lot, rebased. |
|
to other maintainers; having a quick look at this one; don't merge yet 😅 |
thaJeztah
left a comment
There was a problem hiding this comment.
Left a comment about naming of the option (compression -> compress), and some thoughs
| defer os.RemoveAll(tmp) | ||
| filename := filepath.Join(tmp, "container.log") | ||
| config := map[string]string{"max-file": "2", "max-size": "1k"} | ||
| config := map[string]string{"max-file": "3", "max-size": "1k", "compression": "true"} |
There was a problem hiding this comment.
Before we merge, I'd like to have one final change: can you rename the compression option to compress? (because it no longer takes the name of a compression, but became a boolean)
daemon/logger/loggerutils/logfile.go
Outdated
| marshal logger.MarshalFunc | ||
| createDecoder makeDecoderFunc | ||
| perms os.FileMode | ||
| logPath string |
There was a problem hiding this comment.
nit: could probably be named just "path"
There was a problem hiding this comment.
Actually, given that LogFile.LogPath() seems to be unused, it looks like it's only used in checkCapacityAndRotate(). Can't we just use f.Name()?
Something like:
diff --git a/daemon/logger/loggerutils/logfile.go b/daemon/logger/loggerutils/logfile.go
index e0f21f46c..428f4786b 100644
--- a/daemon/logger/loggerutils/logfile.go
+++ b/daemon/logger/loggerutils/logfile.go
@@ -79,7 +79,6 @@ func (rc *refCounter) Dereference(fileName string) error {
// LogFile is Logger implementation for default Docker logging.
type LogFile struct {
- logPath string
mu sync.RWMutex // protects the logfile access
f *os.File // store for closing
closed bool
@@ -111,7 +110,6 @@ func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, mar
}
return &LogFile{
- logPath: logPath,
f: log,
capacity: capacity,
currentSize: size,
@@ -162,15 +160,16 @@ func (w *LogFile) checkCapacityAndRotate() error {
if w.currentSize >= w.capacity {
w.rotateMu.Lock()
+ fname := w.f.Name()
if err := w.f.Close(); err != nil {
w.rotateMu.Unlock()
return errors.Wrap(err, "error closing file")
}
- if err := rotate(w.logPath, w.maxFiles, w.compress); err != nil {
+ if err := rotate(fname, w.maxFiles, w.compress); err != nil {
w.rotateMu.Unlock()
return err
}
- file, err := os.OpenFile(w.logPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
+ file, err := os.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
if err != nil {
w.rotateMu.Unlock()
return err
@@ -185,7 +184,7 @@ func (w *LogFile) checkCapacityAndRotate() error {
}
go func() {
- compressFile(w.logPath+".1", w.lastTimestamp)
+ compressFile(fname+".1", w.lastTimestamp)
w.rotateMu.Unlock()
}()
}
@@ -262,11 +261,6 @@ func compressFile(fileName string, lastTimestamp time.Time) {
}
}
-// LogPath returns the location the given writer logs to.
-func (w *LogFile) LogPath() string {
- return w.logPath
-}
-
// MaxFiles return maximum number of files
func (w *LogFile) MaxFiles() int {
return w.maxFilesOr possibly even:
diff --git a/daemon/logger/loggerutils/logfile.go b/daemon/logger/loggerutils/logfile.go
index e0f21f46c..5ad023423 100644
--- a/daemon/logger/loggerutils/logfile.go
+++ b/daemon/logger/loggerutils/logfile.go
@@ -79,7 +79,6 @@ func (rc *refCounter) Dereference(fileName string) error {
// LogFile is Logger implementation for default Docker logging.
type LogFile struct {
- logPath string
mu sync.RWMutex // protects the logfile access
f *os.File // store for closing
closed bool
@@ -111,7 +110,6 @@ func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, mar
}
return &LogFile{
- logPath: logPath,
f: log,
capacity: capacity,
currentSize: size,
@@ -166,11 +164,11 @@ func (w *LogFile) checkCapacityAndRotate() error {
w.rotateMu.Unlock()
return errors.Wrap(err, "error closing file")
}
- if err := rotate(w.logPath, w.maxFiles, w.compress); err != nil {
+ if err := rotate(w.f.Name(), w.maxFiles, w.compress); err != nil {
w.rotateMu.Unlock()
return err
}
- file, err := os.OpenFile(w.logPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
+ file, err := os.OpenFile(w.f.Name(), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
if err != nil {
w.rotateMu.Unlock()
return err
@@ -185,7 +183,7 @@ func (w *LogFile) checkCapacityAndRotate() error {
}
go func() {
- compressFile(w.logPath+".1", w.lastTimestamp)
+ compressFile(w.f.Name()+".1", w.lastTimestamp)
w.rotateMu.Unlock()
}()
}
@@ -262,11 +260,6 @@ func compressFile(fileName string, lastTimestamp time.Time) {
}
}
-// LogPath returns the location the given writer logs to.
-func (w *LogFile) LogPath() string {
- return w.logPath
-}
-
// MaxFiles return maximum number of files
func (w *LogFile) MaxFiles() int {
return w.maxFilesThere was a problem hiding this comment.
It's nice to store it because w.f.Name() can be racey (since w.f will change) unless locking is involved meanwhile the actual path never changes.
There was a problem hiding this comment.
Yes, I was looking at that, seems the only place LogFile.f is written to is during construction in NewLogFile(), and in checkCapacityAndRotate(), and the filename never actually changes (it's always /var/lib/docker/containers/<container-id>/<container-id>-json.log).
Perhaps the first option, using an intermediate variable (fname := w.f.Name()) would work then.
There was a problem hiding this comment.
Perhaps the first option, using an intermediate variable (fname := w.f.Name()) would work then.
I think what @thaJeztah said makes sense if LogPath() is removed. @cpuguy83 WDYT?
There was a problem hiding this comment.
This works, but still need to lock to get the file name.
There was a problem hiding this comment.
@cpuguy83 Yeah, we can get the file name after w.rotateMu.Lock().
daemon/logger/loggerutils/logfile.go
Outdated
| return nil | ||
| } | ||
|
|
||
| extension := "" |
There was a problem hiding this comment.
nit: could be
var extension string|
|
||
| //NewLogFile creates new LogFile | ||
| func NewLogFile(logPath string, capacity int64, maxFiles int, marshaller logger.MarshalFunc, decodeFunc makeDecoderFunc, perms os.FileMode) (*LogFile, error) { | ||
| func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, marshaller logger.MarshalFunc, decodeFunc makeDecoderFunc, perms os.FileMode) (*LogFile, error) { |
There was a problem hiding this comment.
Not always a fan of boolean arguments; was thinking if this should take the name of an actual compression algorithm (none/gzip/flate), or a compression function (similar to decodeFunc) but not sure we'll be adding other compressions in future, so guess this is okay
daemon/logger/loggerutils/logfile.go
Outdated
| } | ||
|
|
||
| // LogPath returns the location the given writer logs to. | ||
| func (w *LogFile) LogPath() string { |
There was a problem hiding this comment.
This doesn't seem to be used anywhere, correct?
There was a problem hiding this comment.
Yeah, I guess this isn't used anymore.
daemon/logger/loggerutils/logfile.go
Outdated
| } | ||
|
|
||
| go func() { | ||
| compressFile(w.logPath+".1", w.lastTimestamp) |
There was a problem hiding this comment.
So, only time this could be a problem is if the next rotation takes place before compression is completed; not sure if that's a real issue, so just thinking out loud. I did some extreme test;
diff --git a/daemon/logger/loggerutils/logfile.go b/daemon/logger/loggerutils/logfile.go
index e0f21f46c..40bbba485 100644
--- a/daemon/logger/loggerutils/logfile.go
+++ b/daemon/logger/loggerutils/logfile.go
@@ -254,7 +254,7 @@ func compressFile(fileName string, lastTimestamp time.Time) {
// Here log the error only and don't return since this is just an optimization.
logrus.Warningf("Failed to marshal JSON: %v", err)
}
-
+ time.Sleep(50 * time.Second)
_, err = pools.Copy(compressWriter, file)
if err != nil {
logrus.WithError(err).WithField("module", "container.logs").WithField("file", fileName).Error("Error compressing log file")Running with that, rotation is waiting for compression to complete, and killing the container became problematic, which is likely expected, just thinking if there would be real-world situations where that would happen.
There was a problem hiding this comment.
Yeah, this should be expected. There's possibly more we can do to prevent blocking on killing the container, but there's already a lot going on with locking for this case.
There was a problem hiding this comment.
Yup, it was probably a bit extreme, just had my "QA hat" on, and seeing what possible things could be problematic. Don't think it should be a blocker for this feature.
|
ping @cpuguy83 could you look at #29932 (comment) if you're ok with changing it to that? |
7448d23 to
61a046e
Compare
|
@cpuguy83 @thaJeztah Updated. |
This PR adds support for compressibility of log file. I added a new option conpression for the jsonfile log driver, this option allows the user to specify compression algorithm to compress the log files. By default, the log files will be not compressed. At present, only support 'gzip'. Signed-off-by: Yanqiang Miao <miao.yanqiang@zte.com.cn> 'docker logs' can read from compressed files Signed-off-by: Yanqiang Miao <miao.yanqiang@zte.com.cn> Add Metadata to the gzip header, optmize 'readlog' Signed-off-by: Yanqiang Miao <miao.yanqiang@zte.com.cn>
61a046e to
f69f09f
Compare
thaJeztah
left a comment
There was a problem hiding this comment.
LGTM!!
thanks for this, I know it took a while 😅
|
hm, looks like this is a flaky test on z; |
|
Thanks @cpuguy83 @thaJeztah 😄 |
Signed-off-by: Yanqiang Miao miao.yanqiang@zte.com.cn
- What I did
This PR adds support for compressibility of log file. I added a new option
conpressionfor thejsonfilelog driver, this option allows the user to specify compression algorithm to compress the log files.- How I did it
The
jsonfilewill compress the history log file, except forcontainer-id-json.log.1, whencompression=gzip. Thecontainer-id-json.log.1is not compressed in order to prevent the log tracking tool from losing some historical log data when a new log file is created.- How to verify it
I run a container as follow:
The results are as follows:
- Description for the changelog
- A picture of a cute animal (not mandatory but encouraged)