* Say what the program was trying to do.
* Make the message unique and searchable.
* Make it detailed.
* FFS, include the filename or whatever else the program is having trouble with.
* If possible, include the source code location.
* If possible, include useful contextual information.
* Quote strings. Once in a while, some unexpected whitespace sneaks in somewhere and this can be hard to figure out.
Eg, don't just abort with "Open failed: NOT_FOUND". Abort with "job.c:2105 Failed to open job description file '/var/spool/jobs/125.json' when processing job #5 for user 'alice': NOT_FOUND".
This way I don't have to strace the damn thing to try and figure out what's it looking for, and know which user it was for, so I don't have to dig around and try and figure out which entry in the database might contain the wrong information.
Also, context-free, generic error messages are awful. A large enough codebase may be impossible to search for some very common keywords.
If possible, googleable error codes are great to have, but they shouldn't replace the error message. It's ideal if you can search the source code and instantly find where the error message originates.