Repository: gohugoio/hugo (language: go)

Issue:
We need to avoid double printing INFO deprecation messages
Which is the situation for warn and error, but the current INFO logging gets very busy if you for some reason want to wait with updating your template:

```
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
INFO  .Site.GoogleAnalytics was deprecated in Hugo v0.120.0 and will be removed in a future release. Use .Site.Config.Services.GoogleAnalytics.ID instead.
```


Discussion / hints:

Fix patch:
diff --git a/commands/commandeer.go b/commands/commandeer.go
index c3e2019b514..5d414b04a29 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -441,11 +441,11 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
 	}
 
 	optsLogger := loggers.Options{
-		Distinct:    true,
-		Level:       level,
-		Stdout:      r.Out,
-		Stderr:      r.Out,
-		StoreErrors: running,
+		DistinctLevel: logg.LevelWarn,
+		Level:         level,
+		Stdout:        r.Out,
+		Stderr:        r.Out,
+		StoreErrors:   running,
 	}
 
 	return loggers.New(optsLogger), nil
diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go
index f6eb72d8f49..dedbc8c98d4 100644
--- a/common/hugo/hugo.go
+++ b/common/hugo/hugo.go
@@ -369,7 +369,7 @@ func DeprecateLevel(item, alternative, version string, level logg.Level) {
 		msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
 	}
 
-	loggers.Log().Logger().WithLevel(level).Logf(msg)
+	loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf(msg)
 }
 
 // We ususally do about one minor version a month.
diff --git a/common/loggers/logger.go b/common/loggers/logger.go
index a50502897b9..bc64ae0e5b7 100644
--- a/common/loggers/logger.go
+++ b/common/loggers/logger.go
@@ -40,7 +40,7 @@ type Options struct {
 	Level              logg.Level
 	Stdout             io.Writer
 	Stderr             io.Writer
-	Distinct           bool
+	DistinctLevel      logg.Level
 	StoreErrors        bool
 	HandlerPost        func(e *logg.Entry) error
 	SuppressStatements map[string]bool
@@ -92,8 +92,8 @@ func New(opts Options) Logger {
 	logHandler = multi.New(handlers...)
 
 	var logOnce *logOnceHandler
-	if opts.Distinct {
-		logOnce = newLogOnceHandler(logg.LevelWarn)
+	if opts.DistinctLevel != 0 {
+		logOnce = newLogOnceHandler(opts.DistinctLevel)
 		logHandler = newStopHandler(logOnce, logHandler)
 	}
 
@@ -137,10 +137,10 @@ func New(opts Options) Logger {
 // NewDefault creates a new logger with the default options.
 func NewDefault() Logger {
 	opts := Options{
-		Distinct: true,
-		Level:    logg.LevelWarn,
-		Stdout:   os.Stdout,
-		Stderr:   os.Stdout,
+		DistinctLevel: logg.LevelWarn,
+		Level:         logg.LevelWarn,
+		Stdout:        os.Stdout,
+		Stderr:        os.Stdout,
 	}
 	return New(opts)
 }
diff --git a/common/loggers/loggerglobal.go b/common/loggers/loggerglobal.go
index 8b8e8cfc282..6fd474a6935 100644
--- a/common/loggers/loggerglobal.go
+++ b/common/loggers/loggerglobal.go
@@ -31,9 +31,9 @@ func InitGlobalLogger(level logg.Level, panicOnWarnings bool) {
 
 	log = New(
 		Options{
-			Level:       level,
-			Distinct:    true,
-			HandlerPost: logHookLast,
+			Level:         level,
+			DistinctLevel: logg.LevelInfo,
+			HandlerPost:   logHookLast,
 		},
 	)
 }
diff --git a/hugolib/site_new.go b/hugolib/site_new.go
index ac59e01c7ab..da9d19f218f 100644
--- a/hugolib/site_new.go
+++ b/hugolib/site_new.go
@@ -117,7 +117,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
 
 		logOpts := loggers.Options{
 			Level:              cfg.LogLevel,
-			Distinct:           true, // This will drop duplicate log warning and errors.
+			DistinctLevel:      logg.LevelWarn, // This will drop duplicate log warning and errors.
 			HandlerPost:        logHookLast,
 			Stdout:             cfg.LogOut,
 			Stderr:             cfg.LogOut,
diff --git a/resources/page/page.go b/resources/page/page.go
index 54fc71f3310..b5af489f1af 100644
--- a/resources/page/page.go
+++ b/resources/page/page.go
@@ -339,8 +339,6 @@ type PageWithoutContent interface {
 	// This is currently only triggered with the Related content feature
 	// and the "fragments" type of index.
 	HeadingsFiltered(context.Context) tableofcontents.Headings
-
-	DeprecatedWarningPageMethods
 }
 
 // Positioner provides next/prev navigation.
@@ -417,7 +415,6 @@ type TableOfContentsProvider interface {
 
 // TranslationsProvider provides access to any translations.
 type TranslationsProvider interface {
-
 	// IsTranslated returns whether this content file is translated to
 	// other language(s).
 	IsTranslated() bool
@@ -431,7 +428,6 @@ type TranslationsProvider interface {
 
 // TreeProvider provides section tree navigation.
 type TreeProvider interface {
-
 	// IsAncestor returns whether the current page is an ancestor of other.
 	// Note that this method is not relevant for taxonomy lists and taxonomy terms pages.
 	IsAncestor(other any) (bool, error)
@@ -469,15 +465,6 @@ type TreeProvider interface {
 	Page() Page
 }
 
-// DeprecatedWarningPageMethods lists deprecated Page methods that will trigger
-// a WARNING if invoked.
-// This was added in Hugo 0.55.
-type DeprecatedWarningPageMethods any // This was emptied in Hugo 0.93.0.
-
-// Move here to trigger ERROR instead of WARNING.
-// TODO(bep) create wrappers and put into the Page once it has some methods.
-type DeprecatedErrorPageMethods any
-
 // PageWithContext is a Page with a context.Context.
 type PageWithContext struct {
 	Page
diff --git a/resources/page/page_generate/generate_page_wrappers.go b/resources/page/page_generate/generate_page_wrappers.go
index abbf02bf1d2..2449cf28dc2 100644
--- a/resources/page/page_generate/generate_page_wrappers.go
+++ b/resources/page/page_generate/generate_page_wrappers.go
@@ -15,13 +15,12 @@ package page_generate
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
 	"reflect"
 
-	"errors"
-
 	"github.com/gohugoio/hugo/common/maps"
 
 	"github.com/gohugoio/hugo/codegen"
@@ -47,8 +46,7 @@ const header = `// Copyright 2019 The Hugo Authors. All rights reserved.
 `
 
 var (
-	pageInterfaceDeprecated = reflect.TypeOf((*page.DeprecatedWarningPageMethods)(nil)).Elem()
-	pageInterface           = reflect.TypeOf((*page.Page)(nil)).Elem()
+	pageInterface = reflect.TypeOf((*page.Page)(nil)).Elem()
 
 	packageDir = filepath.FromSlash("resources/page")
 )
@@ -58,10 +56,6 @@ func Generate(c *codegen.Inspector) error {
 		return fmt.Errorf("failed to generate JSON marshaler: %w", err)
 	}
 
-	if err := generateDeprecatedWrappers(c); err != nil {
-		return fmt.Errorf("failed to generate deprecate wrappers: %w", err)
-	}
-
 	if err := generateFileIsZeroWrappers(c); err != nil {
 		return fmt.Errorf("failed to generate file wrappers: %w", err)
 	}
@@ -81,10 +75,6 @@ func generateMarshalJSON(c *codegen.Inspector) error {
 
 	// Exclude these methods
 	excludes := []reflect.Type{
-		// We need to evaluate the deprecated vs JSON in the future,
-		// but leave them out for now.
-		pageInterfaceDeprecated,
-
 		// Leave this out for now. We need to revisit the author issue.
 		reflect.TypeOf((*page.AuthorProvider)(nil)).Elem(),
 
@@ -133,71 +123,6 @@ package page
 	return nil
 }
 
-func generateDeprecatedWrappers(c *codegen.Inspector) error {
-	filename := filepath.Join(c.ProjectRootDir, packageDir, "page_wrappers.autogen.go")
-	f, err := os.Create(filename)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	// Generate a wrapper for deprecated page methods
-
-	reasons := map[string]string{
-		"IsDraft":        "Use .Draft.",
-		"Hugo":           "Use the global hugo function.",
-		"LanguagePrefix": "Use .Site.LanguagePrefix.",
-		"GetParam":       "Use .Param or .Params.myParam.",
-		"RSSLink": `Use the Output Format's link, e.g. something like:
-    {{ with .OutputFormats.Get "RSS" }}{{ .RelPermalink }}{{ end }}`,
-		"URL": "Use .Permalink or .RelPermalink. If what you want is the front matter URL value, use .Params.url",
-	}
-
-	deprecated := func(name string, tp reflect.Type) string {
-		alternative, found := reasons[name]
-		if !found {
-			panic(fmt.Sprintf("no deprecated reason found for %q", name))
-		}
-
-		return fmt.Sprintf("helpers.Deprecated(%q, %q, true)", "Page."+name, alternative)
-	}
-
-	var buff bytes.Buffer
-
-	methods := c.MethodsFromTypes([]reflect.Type{pageInterfaceDeprecated}, nil)
-
-	for _, m := range methods {
-		fmt.Fprint(&buff, m.Declaration("*pageDeprecated"))
-		fmt.Fprintln(&buff, " {")
-		fmt.Fprintf(&buff, "\t%s\n", deprecated(m.Name, m.Owner))
-		fmt.Fprintf(&buff, "\t%s\n}\n", m.Delegate("p", "p"))
-
-	}
-
-	pkgImports := methods.Imports()
-	// pkgImports := append(methods.Imports(), "github.com/gohugoio/hugo/helpers")
-
-	fmt.Fprintf(f, `%s
-
-package page
-
-%s
-// NewDeprecatedWarningPage adds deprecation warnings to the given implementation.
-func NewDeprecatedWarningPage(p DeprecatedWarningPageMethods) DeprecatedWarningPageMethods {
-	return &pageDeprecated{p: p}
-}
-
-type pageDeprecated struct {
-	p DeprecatedWarningPageMethods
-}
-
-%s
-
-`, header, importsString(pkgImports), buff.String())
-
-	return nil
-}
-
 func generateFileIsZeroWrappers(c *codegen.Inspector) error {
 	filename := filepath.Join(c.ProjectRootDir, packageDir, "zero_file.autogen.go")
 	f, err := os.Create(filename)
diff --git a/resources/page/page_wrappers.autogen.go b/resources/page/page_wrappers.autogen.go
index 55dff47d5ab..4b7c034a171 100644
--- a/resources/page/page_wrappers.autogen.go
+++ b/resources/page/page_wrappers.autogen.go
@@ -14,12 +14,3 @@
 // This file is autogenerated.
 
 package page
-
-// NewDeprecatedWarningPage adds deprecation warnings to the given implementation.
-func NewDeprecatedWarningPage(p DeprecatedWarningPageMethods) DeprecatedWarningPageMethods {
-	return &pageDeprecated{p: p}
-}
-
-type pageDeprecated struct {
-	p DeprecatedWarningPageMethods
-}


Test patch:
diff --git a/common/loggers/logger_test.go b/common/loggers/logger_test.go
index 1b42eff53a6..6f589aafe99 100644
--- a/common/loggers/logger_test.go
+++ b/common/loggers/logger_test.go
@@ -29,10 +29,10 @@ func TestLogDistinct(t *testing.T) {
 	c := qt.New(t)
 
 	opts := loggers.Options{
-		Distinct:    true,
-		StoreErrors: true,
-		Stdout:      io.Discard,
-		Stderr:      io.Discard,
+		DistinctLevel: logg.LevelWarn,
+		StoreErrors:   true,
+		Stdout:        io.Discard,
+		Stderr:        io.Discard,
 	}
 
 	l := loggers.New(opts)
@@ -85,7 +85,6 @@ func TestOptionStoreErrors(t *testing.T) {
 
 	c.Assert(sb.String(), qt.Contains, "error 1")
 	c.Assert(sb.String(), qt.Contains, "ERROR")
-
 }
 
 func TestLogCount(t *testing.T) {
@@ -124,17 +123,16 @@ func TestSuppressStatements(t *testing.T) {
 	c.Assert(errorsStr, qt.Not(qt.Contains), "error 1")
 	c.Assert(errorsStr, qt.Contains, "error 2")
 	c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 1)
-
 }
 
 func TestReset(t *testing.T) {
 	c := qt.New(t)
 
 	opts := loggers.Options{
-		StoreErrors: true,
-		Distinct:    true,
-		Stdout:      io.Discard,
-		Stderr:      io.Discard,
+		StoreErrors:   true,
+		DistinctLevel: logg.LevelWarn,
+		Stdout:        io.Discard,
+		Stderr:        io.Discard,
 	}
 
 	l := loggers.New(opts)
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 54085070c9d..14509657a50 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -394,10 +394,10 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 
 		logger := loggers.New(
 			loggers.Options{
-				Stdout:   w,
-				Stderr:   w,
-				Level:    s.Cfg.LogLevel,
-				Distinct: true,
+				Stdout:        w,
+				Stderr:        w,
+				Level:         s.Cfg.LogLevel,
+				DistinctLevel: logg.LevelWarn,
 			},
 		)
 
diff --git a/testscripts/commands/deprecate.txt b/testscripts/commands/deprecate.txt
index b15d7ec841e..3be4976d569 100644
--- a/testscripts/commands/deprecate.txt
+++ b/testscripts/commands/deprecate.txt
@@ -1,13 +1,13 @@
 
+# Test deprecation logging.
+hugo -e info --logLevel info
+stdout 'INFO  deprecated: item was deprecated in Hugo'
 
-hugo -e info  --logLevel info
-stdout 'INFO  item was deprecated in Hugo'
+hugo -e warn --logLevel warn
+stdout 'WARN  deprecated: item was deprecated in Hugo'
 
-hugo -e warn  --logLevel warn
-stdout 'WARN  item was deprecated in Hugo'
-
-! hugo -e error  --logLevel warn
-stdout 'ERROR item was deprecated in Hugo'
+! hugo -e error --logLevel warn
+stdout 'ERROR deprecated: item was deprecated in Hugo'
 
 -- hugo.toml --
 baseURL = "https://example.com/"