2020-01-08 05:28:43 -08:00
|
|
|
// Copyright 2020 The Prometheus Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package logging
|
|
|
|
|
|
|
|
import (
|
fix!: stop unbounded memory usage from query log
Resolves: #15433
When I converted prometheus to use slog in #14906, I update both the
`QueryLogger` interface, as well as how the log calls to the
`QueryLogger` were built up in `promql.Engine.exec()`. The backing
logger for the `QueryLogger` in the engine is a
`util/logging.JSONFileLogger`, and it's implementation of the `With()`
method updates the logger the logger in place with the new keyvals added
onto the underlying slog.Logger, which means they get inherited onto
everything after. All subsequent calls to `With()`, even in later
queries, would continue to then append on more and more keyvals for the
various params and fields built up in the logger. In turn, this causes
unbounded growth of the logger, leading to increased memory usage, and
in at least one report was the likely cause of an OOM kill. More
information can be found in the issue and the linked slack thread.
This commit does a few things:
- It was referenced in feedback in #14906 that it would've been better
to not change the `QueryLogger` interface if possible, this PR
proposes changes that bring it closer to alignment with the pre-3.0
`QueryLogger` interface contract
- reverts `promql.Engine.exec()`'s usage of the query logger to the
pattern of building up an array of args to pass at once to the end log
call. Avoiding the repetitious calls to `.With()` are what resolve the
issue with the logger growth/memory usage.
- updates the scrape failure logger to use the update `QueryLogger`
methods in the contract.
- updates tests accordingly
- cleans up unused methods
Builds and passes tests successfully. Tested locally and confirmed I
could no longer reproduce the issue/it resolved the issue.
Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
2024-11-23 11:20:37 -08:00
|
|
|
"context"
|
2022-06-27 09:16:58 -07:00
|
|
|
"fmt"
|
2024-11-23 20:30:23 -08:00
|
|
|
"io"
|
2024-09-09 18:41:53 -07:00
|
|
|
"log/slog"
|
2020-01-08 05:28:43 -08:00
|
|
|
"os"
|
|
|
|
|
2024-09-09 18:41:53 -07:00
|
|
|
"github.com/prometheus/common/promslog"
|
2020-01-08 05:28:43 -08:00
|
|
|
)
|
|
|
|
|
2024-11-23 20:30:23 -08:00
|
|
|
var _ slog.Handler = (*JSONFileLogger)(nil)
|
|
|
|
|
|
|
|
var _ io.Closer = (*JSONFileLogger)(nil)
|
|
|
|
|
|
|
|
// JSONFileLogger represents a logger that writes JSON to a file. It implements
|
|
|
|
// the slog.Handler interface, as well as the io.Closer interface.
|
2020-01-08 05:28:43 -08:00
|
|
|
type JSONFileLogger struct {
|
2024-11-23 20:30:23 -08:00
|
|
|
handler slog.Handler
|
|
|
|
file *os.File
|
2020-01-08 05:28:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewJSONFileLogger returns a new JSONFileLogger.
|
|
|
|
func NewJSONFileLogger(s string) (*JSONFileLogger, error) {
|
|
|
|
if s == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2021-10-22 01:06:44 -07:00
|
|
|
f, err := os.OpenFile(s, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
|
2020-01-08 05:28:43 -08:00
|
|
|
if err != nil {
|
2024-09-09 18:41:53 -07:00
|
|
|
return nil, fmt.Errorf("can't create json log file: %w", err)
|
2020-01-08 05:28:43 -08:00
|
|
|
}
|
|
|
|
|
2024-09-09 18:41:53 -07:00
|
|
|
jsonFmt := &promslog.AllowedFormat{}
|
|
|
|
_ = jsonFmt.Set("json")
|
2020-01-08 05:28:43 -08:00
|
|
|
return &JSONFileLogger{
|
2024-11-23 20:30:23 -08:00
|
|
|
handler: promslog.New(&promslog.Config{Format: jsonFmt, Writer: f}).Handler(),
|
|
|
|
file: f,
|
2020-01-08 05:28:43 -08:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-11-23 20:30:23 -08:00
|
|
|
// Close closes the underlying file. It implements the io.Closer interface.
|
2020-01-08 05:28:43 -08:00
|
|
|
func (l *JSONFileLogger) Close() error {
|
|
|
|
return l.file.Close()
|
|
|
|
}
|
|
|
|
|
2024-11-23 20:30:23 -08:00
|
|
|
// Enabled returns true if and only if the internal slog.Handler is enabled. It
|
|
|
|
// implements the slog.Handler interface.
|
|
|
|
func (l *JSONFileLogger) Enabled(ctx context.Context, level slog.Level) bool {
|
|
|
|
return l.handler.Enabled(ctx, level)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle takes record created by an slog.Logger and forwards it to the
|
|
|
|
// internal slog.Handler for dispatching the log call to the backing file. It
|
|
|
|
// implements the slog.Handler interface.
|
|
|
|
func (l *JSONFileLogger) Handle(ctx context.Context, r slog.Record) error {
|
|
|
|
return l.handler.Handle(ctx, r.Clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithAttrs returns a new *JSONFileLogger with a new internal handler that has
|
|
|
|
// the provided attrs attached as attributes on all further log calls. It
|
|
|
|
// implements the slog.Handler interface.
|
|
|
|
func (l *JSONFileLogger) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
|
|
if len(attrs) == 0 {
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
return &JSONFileLogger{file: l.file, handler: l.handler.WithAttrs(attrs)}
|
2024-09-09 18:41:53 -07:00
|
|
|
}
|
|
|
|
|
2024-11-23 20:30:23 -08:00
|
|
|
// WithGroup returns a new *JSONFileLogger with a new internal handler that has
|
|
|
|
// the provided group name attached, to group all other attributes added to the
|
|
|
|
// logger. It implements the slog.Handler interface.
|
|
|
|
func (l *JSONFileLogger) WithGroup(name string) slog.Handler {
|
|
|
|
if name == "" {
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
return &JSONFileLogger{file: l.file, handler: l.handler.WithGroup(name)}
|
2020-01-08 05:28:43 -08:00
|
|
|
}
|