Rez, touch to log, then touch again to listen back when all is quiet.
Or pick up and re-rez somewhere quieter.
From a recent test:
(unedited lines of output from longer log)
Alisa Honey: only if i get a copy if it works loll
Patch Lamington: loggin is on...
Alisa Honey: hello logger
Patch Lamington: I will be making it public domain....
Patch Lamington: to replace the web tool recently lost
Patch Lamington: though it will need more work to prevent out of memory errors

Alisa Honey: i could use one when i teach my classes its a pian copying and pasteing
Patch Lamington: sure... currently it has playback by ownersay, but that could be changed
Patch Lamington: everyone say YEAH!
Alisa Honey: yeah loll
Dallas Harrington: yeah
CODE
list names;
list speech;
list colours=["002EB8","FF6633","006600","660066","660033","663300","1A9900","FF14B1","001A99","#B88A00"];
list unique_names;
default
{
state_entry()
{
llSetText("This is a chat logger - currently only for testing",<0,0,0>,1.0);
integer i;
integer c;
for (i=0;i<llGetListLength(names);i++)
{
c = llListFindList(unique_names,llList2List(names,i,i));
while (c >= llGetListLength(colours)) // dont crash if I run out of colours
c -= llGetListLength(colours);
llSetObjectName("" + llList2String(names,i) );
llOwnerSay( llList2String(speech,i) + "" );
}
names = [];
speech = [];
unique_names = [];
llSetObjectName("Patch's Funky Chat Logger");
}
on_rez(integer i)
{
llResetScript();
}
touch_start(integer total_number)
{
if (llDetectedKey(0) == llGetOwner() )
{
llSay(0, "logging on!.");
state logging_chat;
}
}
}
state logging_chat
{
state_entry()
{
llListen(0,"",NULL_KEY,"");
}
on_rez(integer i)
{
llOwnerSay("Logging still on! Touch to get playback");
}
touch_start(integer total_number)
{
if (llDetectedKey(0) == llGetOwner() )
{
llSay(0, "chat logging now off - replaying log!.");
state default;
}
}
listen(integer channel, string name, key id, string message)
{
if(llListFindList(unique_names,[name]) == -1)
{
unique_names += name;
}
names += name;
speech += message;
}
}