Files
Jesse HallamandGitHub 8bb95b7148 MM-68378: Fix silent bulk failures in OpenSearch/Elasticsearch indexers (#36189)
- Register per-item OnFailure callback to log each rejected document
  with its error type and reason
- Restructure DoJob to close the bulk processor before marking a job
  successful; check Stats().NumFailed after close and fail the job
  when any documents were not indexed
- Use a sync.Once-guarded doClose helper so the processor is flushed
  exactly once across all exit paths; non-success paths return and let
  the defer handle the close
- Close function owns all its logging: warn with error on failure, info
  on success; callers no longer need to log close errors themselves
- Fix ResponseBodyEnabled() hardcoded false → return l.trace so
  per-item error JSON is visible when Trace=all is set
- Set job to error (rather than silently return) when createBulkProcessor
  fails after the job has been claimed
- Add integration tests for both backends that apply a write block to
  verify bulk failures are surfaced as job errors
- Add i18n strings for the two new error codes
2026-04-21 09:20:58 -04:00

90 lines
2.1 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.enterprise for license information.
package common
import (
"fmt"
"net/http"
"time"
"github.com/mattermost/logr/v2"
"github.com/mattermost/mattermost/server/public/shared/mlog"
)
func NewLogger(backend string, mlogger mlog.LoggerIFace, trace bool) *Logger {
return &Logger{
backend: backend,
logger: mlogger,
trace: trace,
}
}
// Logger is a target to pass to the Logger instance of the search backend.
type Logger struct {
backend string
trace bool
logger mlog.LoggerIFace
}
// LogRoundTrip prints the information about request and response.
func (l *Logger) LogRoundTrip(req *http.Request, res *http.Response, err error, start time.Time, dur time.Duration) error {
// Set error level.
// 0 = debug, 1=warn, 2=error
var level int
switch {
case err != nil:
level = 2
case res != nil && res.StatusCode > 0 && res.StatusCode < 500:
level = 0
case res != nil && res.StatusCode > 499:
level = 2
default:
level = 2
}
// Capture fields.
fields := []mlog.Field{
mlog.String("method", req.Method),
mlog.Int("status_code", res.StatusCode),
mlog.String("duration", dur.String()),
mlog.String("url", req.URL.String()),
}
var logFn func(string, ...logr.Field)
switch level {
case 0:
logFn = l.logger.Debug
case 1:
logFn = l.logger.Warn
case 2:
logFn = l.logger.Error
}
logFn(l.backend+" request", fields...)
return nil
}
// RequestBodyEnabled makes the client pass request body to logger
func (l *Logger) RequestBodyEnabled() bool { return l.trace }
// ResponseBodyEnabled makes the client pass response body to logger
func (l *Logger) ResponseBodyEnabled() bool { return l.trace }
func NewBulkIndexerLogger(mlogger mlog.LoggerIFace, name string) BulkIndexerDebugLogger {
return BulkIndexerDebugLogger{
logger: mlogger,
name: name,
}
}
type BulkIndexerDebugLogger struct {
logger mlog.LoggerIFace
name string
}
func (bl BulkIndexerDebugLogger) Printf(str string, params ...any) {
line := fmt.Sprintf(str, params...)
bl.logger.Debug(line, mlog.String("workername", bl.name))
}