The simplest answers seem to be
- store the timestamp with greater precision, or
- store the timestamp to the second and retry (with a slightly later timestamp) if INSERT fails because of a duplicate key.
None of the three ideas you mention have anything to do with normalization. These are decisions about what to store; at the conceptual level, you normalize after you decide what to store. What the row means (so, what each column means) is significant; these meanings make up the table's predicate. The predicate lets you derive new true facts from older true facts.
Using an integer as a surrogate key, you're unlikely to exhaust the key space. But you still have to declare the natural key, so a surrogate in this case doesn't do anything useful for you.
Adding a "count" colummn makes sense if it makes sense to count things; otherwise it doesn't. Look at these two examples.
Timestamp ActionType Source Target
--
2013-02-02 08:00:01 Wibble SysA SysB
2013-02-02 08:00:02 Wibble SysA SysB
Timestamp ActionType Source Target Count
--
2013-02-02 08:00:01 Wibble SysA SysB 2
What's the difference in meaning here? The meaning of "Timestamp" is particularly important. Normalization is based on semantics; what you need to do depends on what the data means, not on what the columns are named.
Breaking events into small groups might make sense (like adding a "count" column might make sense) if groups of events have meaning in your system.