Repository: caddyserver/caddy (language: go)

Issue:
Support changing the internal server name/id via Caddyfile
It doesn't seem to be possible to define the `server` names via Caddyfile for better identification through logs or metrics.

When using Caddyfile format, servers are named following a `srvX` pattern and when multiple sites are defined it becomes hard to identify which one it is referring to in logs or metrics

This is currently possible via the JSON [servers](https://caddyserver.com/docs/json/apps/http/servers/) keys.


Discussion / hints:
What's your use case for needing the JSON key names when using Caddyfile? They are considered an implementation detail, and irrelevant when using Caddyfile. They're really just for your convenience of reading JSON config.
I'm aware of https://github.com/caddyserver/caddy/issues/3784 which is kinda related. My use case is more related to logs / metrics where these are used as identifiers.

#### My use case

We are building a Caddy lb policy as a module to implement shuffle sharding internally. The way we are doing it is by defining a `main` site that has a `reverse_proxy` with upstreams being the same caddy instance for other purposes.

```
:8080 {
...
}

:8081 {
...
}

:8082 {
...
}

main {
  reverse_proxy {
    to localhost:8080 localhost:8081 localhost:8082
    lb_policy shuffle_shard <custom_module_options>
  }
}

```

The thing is when we instrument our metrics or logs, I get `srv0`, `srv1`, `srv2` and `srv3` as identifiers and those are hard to map to the right servers.

If I could, via Caddyfile, set a custom name to the server, these would be mapped to logs and metrics and I could easily filter based on that.

If there was a directive I could set inside each of these sites to replace the internal server name, that would be the easiest path from my limited understanding.
You can use python scripts to automate label changing, just using python subprocess to call caddy adapt and parse json config from subprocess stdout and modify config and feed it to caddy or save it to a file and let caddy run from it.

Parsing Caddyfile is a lot more difficult than it seems.
The problem with this is that servers are not 1:1 with the Caddyfile. There's risk that attempting to change the name could apply to more than one server, which would cause an invalid config because of duplicate JSON keys.
> The problem with this is that servers are not 1:1 with the Caddyfile. There's risk that attempting to change the name could apply to more than one server, which would cause an invalid config because of duplicate JSON keys.

That's why testing is important. Messing with raw json config is always a risk.

To be fair, using python or any other program languages to write json with built-in json library, there can't be duplicated keys.
Would you be able to pinpoint in the code where the `srvX` logic is applied when using Caddyfile instead of raw JSON as the config adapter? I'm far from understanding if this is even possible given the feedback from y'all but I'd love to take a look at it!
The server name gets set here:

https://github.com/caddyserver/caddy/blob/c38a040e85a7d277299acaab39bdad171bc45675/caddyconfig/httpcaddyfile/httptype.go#L704

But the code to get to that point is very complex. There's a lot of sorting, organizing, disambiguating of the site blocks to get to "server pairings" and then it gets assembled.

Fix patch:
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 00678ef2420..43d756f60cf 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -706,7 +706,7 @@ func (st *ServerType) serversFromPairings(
 
 	err := applyServerOptions(servers, options, warnings)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("applying global server options: %v", err)
 	}
 
 	return servers, nil
diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go
index 7aa0a2a8c6e..dca4ede772c 100644
--- a/caddyconfig/httpcaddyfile/serveroptions.go
+++ b/caddyconfig/httpcaddyfile/serveroptions.go
@@ -33,6 +33,7 @@ type serverOptions struct {
 	ListenerAddress string
 
 	// These will all map 1:1 to the caddyhttp.Server struct
+	Name                 string
 	ListenerWrappersRaw  []json.RawMessage
 	ReadTimeout          caddy.Duration
 	ReadHeaderTimeout    caddy.Duration
@@ -58,6 +59,15 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
 		}
 		for nesting := d.Nesting(); d.NextBlock(nesting); {
 			switch d.Val() {
+			case "name":
+				if serverOpts.ListenerAddress == "" {
+					return nil, d.Errf("cannot set a name for a server without a listener address")
+				}
+				if !d.NextArg() {
+					return nil, d.ArgErr()
+				}
+				serverOpts.Name = d.Val()
+
 			case "listener_wrappers":
 				for nesting := d.Nesting(); d.NextBlock(nesting); {
 					modID := "caddy.listeners." + d.Val()
@@ -248,7 +258,22 @@ func applyServerOptions(
 		return nil
 	}
 
-	for _, server := range servers {
+	// check for duplicate names, which would clobber the config
+	existingNames := map[string]bool{}
+	for _, opts := range serverOpts {
+		if opts.Name == "" {
+			continue
+		}
+		if existingNames[opts.Name] {
+			return fmt.Errorf("cannot use duplicate server name '%s'", opts.Name)
+		}
+		existingNames[opts.Name] = true
+	}
+
+	// collect the server name overrides
+	nameReplacements := map[string]string{}
+
+	for key, server := range servers {
 		// find the options that apply to this server
 		opts := func() *serverOptions {
 			for _, entry := range serverOpts {
@@ -287,6 +312,16 @@ func applyServerOptions(
 			}
 			server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
 		}
+
+		if opts.Name != "" {
+			nameReplacements[key] = opts.Name
+		}
+	}
+
+	// rename the servers if marked to do so
+	for old, new := range nameReplacements {
+		servers[new] = servers[old]
+		delete(servers, old)
 	}
 
 	return nil


Test patch:
diff --git a/caddytest/integration/caddyfile_adapt/server_names.txt b/caddytest/integration/caddyfile_adapt/server_names.txt
new file mode 100644
index 00000000000..e43eb8c03a4
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/server_names.txt
@@ -0,0 +1,77 @@
+{
+	servers :443 {
+		name https
+	}
+
+	servers :8000 {
+		name app1
+	}
+
+	servers :8001 {
+		name app2
+	}
+
+	servers 123.123.123.123:8002 {
+		name bind-server
+	}
+}
+
+example.com {
+}
+
+:8000 {
+}
+
+:8001, :8002 {
+}
+
+:8002 {
+	bind 123.123.123.123 222.222.222.222
+}
+----------
+{
+	"apps": {
+		"http": {
+			"servers": {
+				"app1": {
+					"listen": [
+						":8000"
+					]
+				},
+				"app2": {
+					"listen": [
+						":8001"
+					]
+				},
+				"bind-server": {
+					"listen": [
+						"123.123.123.123:8002",
+						"222.222.222.222:8002"
+					]
+				},
+				"https": {
+					"listen": [
+						":443"
+					],
+					"routes": [
+						{
+							"match": [
+								{
+									"host": [
+										"example.com"
+									]
+								}
+							],
+							"terminal": true
+						}
+					]
+				},
+				"srv4": {
+					"listen": [
+						":8002"
+					]
+				}
+			}
+		}
+	}
+}
\ No newline at end of file