Field types are decided by whichever document arrives first, so a field that is an integer in one service and a string in another is rejected in the second — silently.
PUT /_template/logs
{
"index_patterns": ["logs-*"],
"settings": { "number_of_shards": 1, "number_of_replicas": 1 },
"mappings": {
"dynamic_templates": [
{ "strings": { "match_mapping_type": "string",
"mapping": { "type": "keyword", "ignore_above": 1024 }}}
],
"properties": { "@timestamp": { "type": "date" } }
}
}
Defaulting strings to keyword rather than analysed text is the decision worth making up front: analysed text is for prose, and a hostname or a status code is a value to filter and aggregate on. Getting it wrong doubles the index size and makes exact matching unreliable. Changing a mapping after documents exist means a reindex, which is why the template has to precede the first write.