Instead of messy Roll20 chat output markup that cannot be modified later, e.g. to inject a neat button to roll back a particular XP gain or loss, it would be better to store the entire XP log as a list of structs, serialize()d into the storage attribute. That way, the data is clean and gets marked up only for output, with whatever bells and whistles the output logic might want to add or not.
xpLog := { xpDiff: logText }, { xpDiff: logText }, ...
where xpDiff is a signed integer (+5 or -5, for example) and logText is a string holding the reason for the change
Validation: Summing up xpDiff through the entire list must be exactly the same as the XP attribute at any given time.
Instead of messy Roll20 chat output markup that cannot be modified later, e.g. to inject a neat button to roll back a particular XP gain or loss, it would be better to store the entire XP log as a list of structs, serialize()d into the storage attribute. That way, the data is clean and gets marked up only for output, with whatever bells and whistles the output logic might want to add or not.