You could build an internal counter with the precision of your task cycle.
Let's say you have a plc cycle of 10ms, you can log your data with a 10ms timestamp.
If you want to be more precise, you can either go down with cycle time of your main plc task or create a separate task with a faster cycle.
Another possibility is to use fast IOs with oversampling like the el1262.
Here as a raw example of how you could define your internal counter:
Declaration part:
PROGRAM MAIN
VAR
bInit : BOOL;
nTime : UINT;
tBufferTime : TIME;
dtBufferDT : DT;
nCalcBuffer : UDINT;
sMs : STRING;
sLogTime : STRING;
sLogTimeWithMs : STRING;
stSystemTime : TIMESTRUCT;
fbLocalTime : FB_LocalSystemTime;
END_VAR
Implementation part:
fbLocalTime(bEnable := TRUE);
IF NOT bInit
THEN
dtBufferDT := SYSTEMTIME_TO_DT(fbLocalTime.systemTime);
IF fbLocalTime.bValid
THEN
bInit := TRUE;
END_IF
ELSE
nTime := nTime + 1;
tBufferTime := UINT_TO_TIME(nTime*10);
IF tBufferTime = T#1S
THEN
//Add a second to your system time
ntime := 0;
nCalcBuffer := DT_TO_UDINT(dtBufferDT)+1;
dtBufferDT := UDINT_TO_DT(nCalcBuffer);
sLogTime := DT_TO_STRING(dtBufferDT);
sLogTimeWithMs := sLogTime;
ELSE
//Add ms string time-stamp
sMs := TIME_TO_STRING(tBufferTime);
sLogTimeWithMs := CONCAT(sLogTime,sMs);
END_IF
END_IF
sLogTimeWithMs will show something like this:
'DT#2019-09-21-14:30:28T#530ms'
giving you the correct time with a 10ms precision without any need of further synchronization.
You can then polish the string in order to clear it from unwanted chars like DT#,T# or ms.