Log every command typed in any shell: output (from logger function to syslog-ng/journald) contains duplicate entries for commands?

You have a lot going on there..... My best answer to this is to explain simply how I have seen session logging done in the past. Hopefully that will give you some options to explore.

  1. As you have already mentioned, pulling the bash history from the user accounts. This only works after the session has ended. Not really the best option but it's easy and reliable.
  2. Using a virtual terminal such as the screen command in Linux. This is not very robust as it starts on the user login however if they know it's being logged you can still kill the service. This works well in an end-user scenario. End users generally are trapped in a specified area anyway and don't have the knowledge to get around this.
  3. Pam_tty_audit module & aureport --tty This is a tool that allows you to specify which users get logged and allow you to specify the storage location of said logs... as always keep the logs off of the host server. I have the session logs on our SFTP server being copied off to a central logging server and a local cronjob moving them to a non shared location for archive.

This is built in for RedHat and Fedora however you can install it on Debian and Ubuntu. It's part of the auditd package I believe. Here is some documentation on auditd and the required configuration changes to pam (in /etc/pam.d/system-auth), specifying a single user here(root):

session required pam_tty_audit.so disable=* enable=root

Example output of aureport --tty:

TTY Report
===============================================
# date time event auid term sess comm data
===============================================
1. 1/29/2014 00:08:52 122249 0000 ? 4686960298 bash "ls -la",<ret> 

This is solution which takes care of the first question, as well as introduces the use of auditd interactively, outside of the pam_tty module solution provided in the other answer.

bash

First, as explained by a contributor, there might be syntax issues with the original setup and there is a better way to do this using the $BASH_COMMAND variable:

The command currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap.

Updating the original prompt_command reference and the function like so works:

PROMPT_COMMAND=$(history -a)
typeset -r PROMPT_COMMAND

function log2syslog
{
   declare command
   command=$BASH_COMMAND
   logger -p local1.notice -t bash -i -- $USER : $command

}
trap log2syslog DEBUG

So the new history lines are written to the bash_history file every time because of PROMPT_COMMAND, and since $BASH_COMMAND is in the trap, the command typed on the cli is the command being executed. If I remove the history -a, I can see my PS1 being echoed. Works perfectly and removes all the duplicate lines. The output includes also alias expansion for some reason.

It is also possible to do this without the trap using PROMPT_COMMAND only, like so:

PROMPT_COMMAND='history -w; history -a; history -r; command=$(fc -ln 0); logger -p local1.notice -t bash -i -- $USER : $command'

It doesn't show alias expansion, but it has a small defect I can't correct: if you just press enter with nothing else on the line, it outputs the last command to logs. You can't merge the arwn history options. We write the history we have to file, then write the appended history since the beginning of the session, then we read it back, and then we look for the last line.

zsh

With zsh we can use the precmd builtin function, similar to P_C, like so, with one shell specific shell option, all in .zshrc:

 setopt incappendhistory

 precmd () {
    command="$(fc -n -e - -l -1)"
    logger -p local1.notice -t zsh -i "$USER : $command"
}

And that's it!


Audit

Audit is an auditing package containing a daemon with plugins and reporting facilities. It uses a rules based approach (see security oriented audit.rules example here) to trap and log events. Install the package then make sure you have this in /etc/conf.d/auditd:

AUDITD_LANG=en_US
AUDITD_DISABLE_CONTEXTS="no"

You can also review many other options in /etc/audit/auditd.conf, including the log path(/var/log/audit/audit.log by default).

The framework also includes a dispatcher with plugins, including one which has the ability to write directly to syslog if so desired, for convenience. You must first enable it in /etc/audisp/plugins.d/sysconf by enabling active:

active = yes
direction = out
path = builtin_syslog
type = builtin 
args = LOG_INFO
format = string

You will also find the facilities (audisp-remote) in the dispatcher for remote logging but this need not be set to enable the dispatching to syslog here. If you enable the syslog plugin and journald is used instead of syslog, journald will show the output as well as /var/log/audit/audit.log (default setup). Once the setup is complete, you can start the daemon it with auditd -s enable. From the logs:

Started dispatcher: /sbin/audispd pid: 3869
audispd[3869]: priority_boost_parser called with: 4
audispd[3869]: max_restarts_parser called with: 10
audispd[3869]: syslog plugin initialized
audispd[3869]: audispd initialized with q_depth=120 and 1 active plugins
audispd[3869]: node=gentoouser3x86_64 type=DAEMON_START msg=audit(1391089266.449:1498): auditd start, ver=2.1.3 format=raw kernel=3.10.2...res=success
auditd[3867]: Init complete, auditd 2.1.3 listening for events (startup state enable)

you can easily check the status too with auditctl -s:
AUDIT_STATUS: enabled=1 flag=1 pid=3867 rate_limit=0 backlog_limit=64 lost=21 backlog=0

Interactive use doesn't require it, but if you don't have a valid auditd.service unit to manage with systemctl for starting at boot, you may try instead simply including audit=1 as a kernel boot parameter as often this has been implemented at the kernel level.

The framework is now up and running and you can selectively trap events interactively on the cli using auditctl. Tons of options are available. In particular we can monitor EXECVE system calls and see the commands used in the shell as arguments here:

# auditctl -a exit,always -F arch=b64 -S execve
(audit.log output)
type=EXECVE msg=audit(1391090877.859:98): argc=3 a0="ls" a1="--color=auto" a2="-la"
type=CWD msg=audit(1391090877.859:98):  cwd="/root"
type=PATH msg=audit(1391090877.859:98): item=0 name="/bin/ls" inode=4194395 dev=08:34 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1391090877.859:98): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=SYSCALL msg=audit(1391090887.955:99): arch=c000003e syscall=59 success=yes exit=0 a0=1de21f0 a1=1de10c0 a2=1db7960 a3=7fff3691a390 items=2 ppid=3994 pid=4006 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 ses=1 tty=pts2 comm="clear" exe="/usr/bin/clear" key=(null)
type=EXECVE msg=audit(1391090887.955:99): argc=1 a0="clear"
type=CWD msg=audit(1391090887.955:99):  cwd="/root"
type=PATH msg=audit(1391090887.955:99): item=0 name="/usr/bin/clear" inode=1966198 dev=08:34 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL

Here's a sample showing both outputs from our trap function and
auditd appearing in our log using journald (which also shows our logger command in the 'trap'):

bash[4410]: myuser : ls --color=auto
audispd[3869]: node=gentoomyuser3x86_64 type=SYSCALL msg=audit(1391096772.067:120): arch=c000003e syscall=59 success=yes exit=0 a0=8e5a10 a1=8e4a60 a...
audispd[3869]: node=gentoomyuser3x86_64 type=EXECVE msg=audit(1391096772.067:120): argc=2 a0="ls" a1="--color=auto"
audispd[3869]: node=gentoomyuser3x86_64 type=CWD msg=audit(1391096772.067:120):  cwd="/home/myuser"
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096772.067:120): item=0 name="/bin/ls" inode=4194395 dev=08:34 mode=010075...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096772.067:120): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 o...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=EOE msg=audit(1391096772.067:120):
audispd[3869]: node=gentoomyuser3x86_64 type=SYSCALL msg=audit(1391096807.548:121): arch=c000003e syscall=59 success=yes exit=0 a0=8e5c50 a1=8e6430 a...
audispd[3869]: node=gentoomyuser3x86_64 type=EXECVE msg=audit(1391096807.548:121): argc=10 a0="logger" a1="-p" a2="local1.notice" a3="-t" ..." a9="date"
audispd[3869]: node=gentoomyuser3x86_64 type=CWD msg=audit(1391096807.548:121):  cwd="/home/myuser"
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096807.548:121): item=0 name="/usr/bin/logger" inode=1966477 dev=08:34 mod...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096807.548:121): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 o...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=EOE msg=audit(1391096807.548:121):
bash[4415]: myuser : date
audispd[3869]: node=gentoomyuser3x86_64 type=SYSCALL msg=audit(1391096807.549:122): arch=c000003e syscall=59 success=yes exit=0 a0=8e5f40 a1=8e5cd0 a...
audispd[3869]: node=gentoomyuser3x86_64 type=EXECVE msg=audit(1391096807.549:122): argc=1 a0="date"
audispd[3869]: node=gentoomyuser3x86_64 type=CWD msg=audit(1391096807.549:122):  cwd="/home/myuser"
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096807.549:122): item=0 name="/bin/date" inode=4194318 dev=08:34 mode=0100...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096807.549:122): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 o...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=EOE msg=audit(1391096807.549:122):
audispd[3869]: node=gentoomyuser3x86_64 type=SYSCALL msg=audit(1391096838.004:123): arch=c000003e syscall=59 success=yes exit=0 a0=8e6c60 a1=8e6d00 a...
audispd[3869]: node=gentoomyuser3x86_64 type=EXECVE msg=audit(1391096838.004:123): argc=21 a0="logger" a1="-p" a2="local1.notice" a3="-t" a4="bash" a...
audispd[3869]: node=gentoomyuser3x86_64 type=CWD msg=audit(1391096838.004:123):  cwd="/home/myuser"
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096838.004:123): item=0 name="/usr/bin/logger" inode=1966477 dev=08:34 mod...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096838.004:123): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 o...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=EOE msg=audit(1391096838.004:123):
bash[4417]: myuser : aafire -width 82 -height 25 -gamma 1 -floyd_steinberg -font mda14 -driver curses
audispd[3869]: node=gentoomyuser3x86_64 type=SYSCALL msg=audit(1391096838.006:124): arch=c000003e syscall=59 success=yes exit=0 a0=8e6c60 a1=8c3880 a...
audispd[3869]: node=gentoomyuser3x86_64 type=EXECVE msg=audit(1391096838.006:124): argc=12 a0="aafire" a1="-width" a2="82" a3="-height" a4...11="curses"
audispd[3869]: node=gentoomyuser3x86_64 type=CWD msg=audit(1391096838.006:124):  cwd="/home/myuser"
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096838.006:124): item=0 name="/usr/bin/aafire" inode=1594727 dev=08:34 mod...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096838.006:124): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 o...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=EOE msg=audit(1391096838.006:124):
audispd[3869]: node=gentoomyuser3x86_64 type=SYSCALL msg=audit(1391096852.816:125): arch=c000003e syscall=59 success=yes exit=0 a0=8e5870 a1=8e5b20 a...
audispd[3869]: node=gentoomyuser3x86_64 type=EXECVE msg=audit(1391096852.816:125): argc=11 a0="logger" a1="-p" a2="local1.notice" a3="-t" ...su" a10="-"
audispd[3869]: node=gentoomyuser3x86_64 type=CWD msg=audit(1391096852.816:125):  cwd="/home/myuser"
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096852.816:125): item=0 name="/usr/bin/logger" inode=1966477 dev=08:34 mod...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=PATH msg=audit(1391096852.816:125): item=1 name=(null) inode=1613135 dev=08:34 mode=0100755 o...type=NORMAL
audispd[3869]: node=gentoomyuser3x86_64 type=EOE msg=audit(139109

...terminate outputting by simply removing the active rules with auditctl -D or stop the entire auditing with auditd -s disable or auditctl -e 0. We don't get to see all character sequences typed to the terminal using this rule but we log all commands.