Repository: gohugoio/hugo (language: go)

Issue:
ignoreLogs = ['cascade-pattern-with-extension'] emits console error
With this config...

```text
ignoreLogs = ['cascade-pattern-with-extension']

[[cascade]]
[cascade.params]
foo = 'bar'
[cascade._target]
path = '/p1.md'   <-- will trigger the error that we want to suppress
```

...the build completes as expected with status code 0 (no errors), yet the console displays:

> ERROR cascade target path "/p1.md" looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see  https://gohugo.io/methods/page/path/
You can suppress this error by adding the following to your site configuration:
ignoreLogs = ['cascade-pattern-with-extension']


The test below passes (as it should), but needs a log assertion (I couldn't get that to work).

<details>
<summary>test case</summary>

```go
func TestFoo(t *testing.T) {
	files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
ignoreLogs = ['cascade-pattern-with-extension']
[[cascade]]
[cascade.params]
foo = 'bar'
[cascade._target]
path = '/p1.md'
-- layouts/index.html --
{{ .Title }}
-- content/index.md --
---
title: home
---
-- content/p1.md --
---
title: p1
---
`

	b, err := TestE(t, files)
	b.Assert(err, qt.IsNil)
}
```
</details>
<br>

Reference: <https://discourse.gohugo.io/t/ignorelogs-cascade-pattern-with-extension-is-ignored/48383>


Discussion / hints:
>The test below passes (as it should), but needs a log assertion (I couldn't get that to work).

It doesn't log an error, which is correct, which I suspect is the reason you couldn't get it to work.
It may not "log" an error, but an error is displayed in the console:

```text
git clone --single-branch -b hugo-github-issue-12151 https://github.com/jmooring/hugo-testing hugo-github-issue-12151
cd hugo-github-issue-12151
hugo server
```

![image](https://github.com/gohugoio/hugo/assets/335669/be116c1b-b1e2-4625-b8b0-71fb2c1705db)



Yea, right ... we're logging this before we have configured the ... logger.
Thanks for confirming, and please let me know if there's a simple assertion (or example) for testing stdout and stderr.

Fix patch:
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index 469a164cf64..9cafc876e15 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -671,6 +671,13 @@ type Configs struct {
 	configLangs []config.AllProvider
 }
 
+func (c *Configs) Validate(logger loggers.Logger) error {
+	for p := range c.Base.Cascade.Config {
+		page.CheckCascadePattern(logger, p)
+	}
+	return nil
+}
+
 // transientErr returns the last transient error found during config compilation.
 func (c *Configs) transientErr() error {
 	for _, l := range c.LanguageConfigSlice {
@@ -969,7 +976,7 @@ func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.Base
 	})
 
 	for _, v := range decoderSetups {
-		p := decodeConfig{p: p, c: target, fs: fs, logger: logger, bcfg: bcfg}
+		p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
 		if err := v.decode(v, p); err != nil {
 			return fmt.Errorf("failed to decode %q: %w", v.key, err)
 		}
diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go
index 9c1d08144a3..5d31d5d35ad 100644
--- a/config/allconfig/alldecoders.go
+++ b/config/allconfig/alldecoders.go
@@ -18,7 +18,6 @@ import (
 	"strings"
 
 	"github.com/gohugoio/hugo/cache/filecache"
-	"github.com/gohugoio/hugo/common/loggers"
 	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/common/types"
 	"github.com/gohugoio/hugo/config"
@@ -43,11 +42,10 @@ import (
 )
 
 type decodeConfig struct {
-	p      config.Provider
-	c      *Config
-	fs     afero.Fs
-	logger loggers.Logger
-	bcfg   config.BaseConfig
+	p    config.Provider
+	c    *Config
+	fs   afero.Fs
+	bcfg config.BaseConfig
 }
 
 type decodeWeight struct {
@@ -293,7 +291,7 @@ var allDecoderSetups = map[string]decodeWeight{
 		key: "cascade",
 		decode: func(d decodeWeight, p decodeConfig) error {
 			var err error
-			p.c.Cascade, err = page.DecodeCascadeConfig(p.logger, p.p.Get(d.key))
+			p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
 			return err
 		},
 	},
diff --git a/hugolib/site_new.go b/hugolib/site_new.go
index 2e893349799..3bcc307ad4b 100644
--- a/hugolib/site_new.go
+++ b/hugolib/site_new.go
@@ -127,6 +127,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
 			SuppressStatements: conf.IgnoredLogs(),
 		}
 		logger = loggers.New(logOpts)
+
 	}
 
 	memCache := dynacache.New(dynacache.Options{Running: conf.Running(), Log: logger})
@@ -145,6 +146,9 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
 	}
 
 	confm := cfg.Configs
+	if err := confm.Validate(logger); err != nil {
+		return nil, err
+	}
 	var sites []*Site
 
 	ns := &contentNodeShifter{
diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go
index 466b6fe53c8..f2075273af4 100644
--- a/resources/page/page_matcher.go
+++ b/resources/page/page_matcher.go
@@ -98,6 +98,12 @@ func isGlobWithExtension(s string) bool {
 	return strings.Count(last, ".") > 0
 }
 
+func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
+	if logger != nil && isGlobWithExtension(m.Path) {
+		logger.Erroridf("cascade-pattern-with-extension", "cascade target path %q looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see  https://gohugo.io/methods/page/path/", m.Path)
+	}
+}
+
 func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) {
 	buildConfig := func(in any) (map[PageMatcher]maps.Params, any, error) {
 		cascade := make(map[PageMatcher]maps.Params)
@@ -127,9 +133,7 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
 
 		for _, cfg := range cfgs {
 			m := cfg.Target
-			if isGlobWithExtension(m.Path) {
-				logger.Erroridf("cascade-pattern-with-extension", "cascade target path %q looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see  https://gohugo.io/methods/page/path/", m.Path)
-			}
+			CheckCascadePattern(logger, m)
 			c, found := cascade[m]
 			if found {
 				// Merge


Test patch:
diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go
index bb328f76178..b9b80a3a1ed 100644
--- a/hugolib/cascade_test.go
+++ b/hugolib/cascade_test.go
@@ -674,6 +674,8 @@ S1|p1:|p2:p2|
 
 // Issue 11977.
 func TestCascadeExtensionInPath(t *testing.T) {
+	t.Parallel()
+
 	files := `
 -- hugo.toml --
 baseURL = "https://example.org"
@@ -700,3 +702,67 @@ title: "Post 1"
 	b.Assert(err, qt.IsNotNil)
 	b.AssertLogContains(`cascade target path "/posts/post-1.de.md" looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see  https://gohugo.io/methods/page/path/`)
 }
+
+func TestCascadeExtensionInPathIgnore(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+baseURL = "https://example.org"
+ignoreLogs   = ['cascade-pattern-with-extension']
+[languages]
+[languages.en]
+weight = 1
+[languages.de]
+-- content/_index.de.md --
++++
+[[cascade]]
+[cascade.params]
+foo = 'bar'
+[cascade._target]
+path = '/posts/post-1.de.md'
++++
+-- content/posts/post-1.de.md --
+---
+title: "Post 1"
+---
+-- layouts/_default/single.html --
+{{ .Title }}|{{ .Params.foo }}$
+`
+	b := Test(t, files)
+	b.AssertLogNotContains(`looks like a path with an extension`)
+}
+
+func TestCascadConfigExtensionInPath(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+baseURL = "https://example.org"
+[[cascade]]
+[cascade.params]
+foo = 'bar'
+[cascade._target]
+path = '/p1.md'
+`
+	b, err := TestE(t, files)
+	b.Assert(err, qt.IsNotNil)
+	b.AssertLogContains(`looks like a path with an extension`)
+}
+
+func TestCascadConfigExtensionInPathIgnore(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+baseURL = "https://example.org"
+ignoreLogs   = ['cascade-pattern-with-extension']
+[[cascade]]
+[cascade.params]
+foo = 'bar'
+[cascade._target]
+path = '/p1.md'
+`
+	b := Test(t, files)
+	b.AssertLogNotContains(`looks like a path with an extension`)
+}
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 8c7017a8723..acbc093baa2 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -169,10 +169,11 @@ type IntegrationTestBuilder struct {
 	renamedFiles []string
 	renamedDirs  []string
 
-	buildCount int
-	GCCount    int
-	counters   *buildCounters
-	logBuff    lockingBuffer
+	buildCount   int
+	GCCount      int
+	counters     *buildCounters
+	logBuff      lockingBuffer
+	lastBuildLog string
 
 	builderInit sync.Once
 }
@@ -192,21 +193,21 @@ func (b *lockingBuffer) Write(p []byte) (n int, err error) {
 func (s *IntegrationTestBuilder) AssertLogContains(els ...string) {
 	s.Helper()
 	for _, el := range els {
-		s.Assert(s.logBuff.String(), qt.Contains, el)
+		s.Assert(s.lastBuildLog, qt.Contains, el)
 	}
 }
 
 func (s *IntegrationTestBuilder) AssertLogNotContains(els ...string) {
 	s.Helper()
 	for _, el := range els {
-		s.Assert(s.logBuff.String(), qt.Not(qt.Contains), el)
+		s.Assert(s.lastBuildLog, qt.Not(qt.Contains), el)
 	}
 }
 
 func (s *IntegrationTestBuilder) AssertLogMatches(expression string) {
 	s.Helper()
 	re := regexp.MustCompile(expression)
-	s.Assert(re.MatchString(s.logBuff.String()), qt.IsTrue, qt.Commentf(s.logBuff.String()))
+	s.Assert(re.MatchString(s.lastBuildLog), qt.IsTrue, qt.Commentf(s.lastBuildLog))
 }
 
 func (s *IntegrationTestBuilder) AssertBuildCountData(count int) {
@@ -341,7 +342,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
 	s.Helper()
 	_, err := s.BuildE()
 	if s.Cfg.Verbose || err != nil {
-		fmt.Println(s.logBuff.String())
+		fmt.Println(s.lastBuildLog)
 		if s.H != nil && err == nil {
 			for _, s := range s.H.Sites {
 				m := s.pageMap
@@ -352,7 +353,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
 			}
 		}
 	} else if s.Cfg.LogLevel <= logg.LevelDebug {
-		fmt.Println(s.logBuff.String())
+		fmt.Println(s.lastBuildLog)
 	}
 	s.Assert(err, qt.IsNil)
 	if s.Cfg.RunGC {
@@ -364,7 +365,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
 }
 
 func (s *IntegrationTestBuilder) LogString() string {
-	return s.logBuff.String()
+	return s.lastBuildLog
 }
 
 func (s *IntegrationTestBuilder) BuildE() (*IntegrationTestBuilder, error) {
@@ -381,6 +382,7 @@ func (s *IntegrationTestBuilder) Init() *IntegrationTestBuilder {
 	if err := s.initBuilder(); err != nil {
 		s.Fatalf("Failed to init builder: %s", err)
 	}
+	s.lastBuildLog = s.logBuff.String()
 	return s
 }
 
@@ -626,10 +628,11 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
 	s.Helper()
 	defer func() {
 		s.reset()
+		s.lastBuildLog = s.logBuff.String()
+		s.logBuff.Reset()
 	}()
 
 	changeEvents := s.changeEvents()
-	s.logBuff.Reset()
 	s.counters = &buildCounters{}
 	cfg.testCounters = s.counters
 
@@ -643,10 +646,6 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
 	if err != nil {
 		return err
 	}
-	logErrorCount := s.H.NumLogErrors()
-	if logErrorCount > 0 {
-		return fmt.Errorf("logged %d error(s): %s", logErrorCount, s.logBuff.String())
-	}
 
 	return nil
 }