Generating a unique reference number for logging purposes in a custom exception I've come up with a base class for exceptions for this SMS-based service I am working on. The basic idea behind the service is that a user sends a text message to a number, the message requests a particular action to be carried out by an agent that logs into a different website, from which some account information is scraped and spat back out to the user. Small take on QoL. Obviously, things don't always work as intended for different reasons.

When something goes wrong and the returned text message doesn't contain the requested information, I want the user to have a reference number to that error, should the issue persist. To accommodate for that I will keep logs, where I will quickly be able to look up what happened using the reference number. The logs will contain full error information.

Intended use of the custom exceptions:

try:
    service.login(credentials)
except AuthenticationError as e:
    sms_client.send(phone_number, f'{Something went wrong. If this keeps on happening, please contact us with this error ref: {e.reference}')


...

import logging
import uuid


logging.basicConfig(filename='errors.log', level=logging.ERROR)


class ServiceException(Exception):
    def __init__(self, message, url):
        self.reference = str(uuid.uuid4()).split('-')[0]
        error_message = f'{message}: \'{url}\' | error ref: {self.reference}'
        logging.error(error_message)
        super(Exception, self).__init__(error_message)


class AuthenticationError(ServiceException):
    """Ambiguous error prevented successful authentication."""
concerned if cutting the UUID short will keep it unique


Well, you're outputting 32 bits. So you can do ballpark 64k transactions (2^16) before running into Big Trouble, according to the birthday paradox.

If you realistically expect someone to report issues within a day, then the identifier is effectively (day, reference), which looks more attractive, as you can do some tens of thousands of transactions each day rather than over the lifetime of the service. More generally, resolve ties with "last one wins", and then you're comparing "typical distance between collisions" to "typical time for user to report an issue".

You're putting 4 bits of entropy into each hex nybble, which likely will be copy-n-pasted (or forwarded) by the user. If you're working within an 8-character constraint, consider using a scheme like base36 or base64 so each character sends more bits. Without pasting there will be usability limitations, such as eyeballing an 'l' and sending the digit '1'.

If you're not worried about users making up identifiers, consider using (userid, serial_num), or better: (userid, timestamp).