script is a nice utility for recording terminal sessions. It spawns a new shell (by default), and records any activity in that shell:

$ script /tmp/script
Script started, file is /tmp/script
$ ps -u wking | grep script
12102 pts/7    00:00:00 script
12103 pts/7    00:00:00 script
$ pstree 12102
$ exit
Script done, file is /tmp/script

The recorded file stores all the characters sent to the terminal:

$ cat -v /tmp/script
Script started on Mon 10 Dec 2012 08:07:17 AM EST
^[]0;wking@mjolnir:~^G^[[?1034h^[[01;32mwking@mjolnir^[[01;34m ~ $^[[00m ps -u wking | grep script^M
12102 pts/7    00:00:00 ^[[01;31m^[[Kscript^[[m^[[K^M
12103 pts/7    00:00:00 ^[[01;31m^[[Kscript^[[m^[[K^M
^[]0;wking@mjolnir:~^G^[[01;32mwking@mjolnir^[[01;34m ~ $^[[00m pstr^G^M
pstree   pstruct  ^M
^[[01;32mwking@mjolnir^[[01;34m ~ $^[[00m pstr^M
pstree   pstruct  ^M
^[[01;32mwking@mjolnir^[[01;34m ~ $^[[00m pstree 12102^M
^[]0;wking@mjolnir:~^G^[[01;32mwking@mjolnir^[[01;34m ~ $^[[00m exit^M

Script done on Mon 10 Dec 2012 08:07:51 AM EST

You can also record timing information in a separate file (this approach was developed by Joey Hess and posted in Debian bug 68556):

$ script --timing=/tmp/timing /tmp/script
Script started, file is /tmp/script
$ echo hello
$ exit
Script done, file is /tmp/script

The timing file has a “delay in seconds” column and a “number of characters to send column”:

$ cat /tmp/timing
0.671478 67
0.159100 1
0.941919 1
0.149764 1

You can play back a script with timing information:

$ scriptreplay -t /tmp/timing -s /tmp/script

You can also play it back with altered timing (e.g. in slow motion):

$ scriptreplay -t /tmp/timing -s /tmp/script -d 0.5
…half-speed playback playback…

This even works reasonably well with curses applications (emacs, vi, …), but information such as window size is not recorded or replayed. From util-linux's scriptreplay(1):

Since the same information is simply being displayed, scriptreplay is only guaranteed to work properly if run on the same type of terminal the typescript was recorded on. Otherwise, any escape characters in the typescript may be interpreted differently by the terminal to which scriptreplay is sending its output.

This means that if you want interoperable replay, you'll want to do something like

$ reset
xterm 24 80

early on so folks know how to setup their replay environment. If you're using some wonky $TERM, you may even want to post your terminfo:

$ infocmp
xterm|xterm terminal emulator (X Window System),
        am, bce, km, mc5i, mir, msgr, npc, xenl,
        colors#8, cols#80, it#8, lines#24, pairs#64,

The user can compare with their current terminal:

$ infocmp xterm linux
comparing xterm to linux.
    comparing booleans.
        ccc: F:T.
        eo: F:T.
    comparing numbers.
        cols: 80, NULL.
        lines: 24, NULL.
        ncv: NULL, 18.

It would be nice if there was an iconv-style converter to translate between terminal operation encodings, but I haven't found one yet. Screen does something like this internally, and their list of control sequences is a useful reference. I've started work on an escape-sequence-to-HTML converter, in case you want to play around with these conversions in Python.