exec [-cl] [-a name] [command [arguments]]
The exec command has two primary uses in Unix/Linux shells. First, when given a command as an argument, it replaces the current shell process with the specified command - the new program inherits the process ID (PID) and takes over completely. When exec completes, there is no shell to return to because it has been replaced. This is a fundamental difference from normal command execution where the shell creates a child process.
Second, when used without a command, exec can manipulate file descriptors in the current shell, redirecting standard input, output, and error streams. This allows for sophisticated I/O redirection that persists for the life of the shell or script. These two modes make exec essential for shell script optimization, container entry points, logging systems, and process management.
# First, note your current shell PID
echo "Current shell PID: $$"
echo "Current shell: $0"
# Now exec will replace this shell with 'top'
# After this command, the shell is GONE - replaced by top
# You won't return to this shell
exec top
# THIS LINE WILL NEVER EXECUTE
echo "You will never see this message"
# To demonstrate safely, we'll use a wrapper:
# Create a subscript to show replacement
cat > /tmp/test_exec.sh << 'EOF'
#!/bin/bash
echo "Script PID before exec: $$"
echo "About to exec ls command..."
exec ls -la /tmp
echo "This line will never execute"
EOF
chmod +x /tmp/test_exec.sh
/tmp/test_exec.sh
# The script shows ls output but never prints the last echo
Script PID before exec: 12345
About to exec ls command...
total 48
drwxrwxrwt 12 root root 4096 Dec 14 14:30 .
drwxr-xr-x 20 root root 4096 Nov 28 09:12 ..
-rwxr-xr-x 1 user user 123 Dec 14 14:30 test_exec.sh
# The script exits after ls - no return to the script
Never run 'exec command' directly in your interactive shell unless you want to replace your shell with that command. When the command finishes, your terminal session will close!
# Create an application wrapper script
cat > /usr/local/bin/myapp_launcher.sh << 'EOF'
#!/bin/bash
# Application launcher with environment setup
# Set up environment
export APP_HOME="/opt/myapp"
export APP_CONFIG="$APP_HOME/config/production.conf"
export JAVA_OPTS="-Xmx2048m -Xms1024m"
export LOG_DIR="/var/log/myapp"
# Ensure log directory exists
mkdir -p "$LOG_DIR"
# Set working directory
cd "$APP_HOME"
# Log startup
echo "[$(date)] Starting application" >> "$LOG_DIR/startup.log"
# Replace this shell with the actual application
# The wrapper process will be replaced, saving memory
exec java $JAVA_OPTS -jar "$APP_HOME/myapp.jar" --config="$APP_CONFIG"
# Never reached
EOF
chmod +x /usr/local/bin/myapp_launcher.sh
# Compare process trees:
# WITHOUT exec (wasteful):
# ├─myapp_launcher.sh (PID 1000)
# └─java -jar myapp.jar (PID 1001)
# Two processes, wrapper sits idle consuming resources
# WITH exec (efficient):
# └─java -jar myapp.jar (PID 1000)
# One process, wrapper is replaced
# Run the launcher
/usr/local/bin/myapp_launcher.sh
# In startup.log:
[Sun Dec 14 14:35:23 EST 2025] Starting application
# Process list shows only java, not the wrapper:
$ ps aux | grep myapp
user 1000 2.5 15.2 3145728 1048576 ? Sl 14:35 0:15 java -Xmx2048m -Xms1024m -jar /opt/myapp/myapp.jar
This pattern is especially important for init systems, Docker containers, and systemd services. The wrapper does its job (setup) then gets out of the way by replacing itself with the actual application.
# Create a script with comprehensive logging
cat > /tmp/logging_script.sh << 'EOF'
#!/bin/bash
# Redirect all output (stdout) to a log file
# All subsequent echo, printf, etc. go to the log
exec > /var/log/myscript.log
# Redirect all errors (stderr) to an error log
exec 2> /var/log/myscript_errors.log
# Or combine both to same file:
# exec &> /var/log/myscript_all.log
echo "Script started at $(date)"
echo "Running as user: $USER"
# Simulate some work
for i in {1..5}; do
echo "Processing item $i"
sleep 1
done
# This goes to stderr (error log)
echo "Warning: This is a test warning" >&2
echo "Script completed at $(date)"
# All output went to files, not terminal
EOF
chmod +x /tmp/logging_script.sh
/tmp/logging_script.sh
# Check the logs
echo "=== Standard Output Log ==="
cat /var/log/myscript.log
echo "=== Error Log ==="
cat /var/log/myscript_errors.log
=== Standard Output Log ===
Script started at Sun Dec 14 14:40:15 EST 2025
Running as user: craig
Processing item 1
Processing item 2
Processing item 3
Processing item 4
Processing item 5
Script completed at Sun Dec 14 14:40:20 EST 2025
=== Error Log ===
Warning: This is a test warning
This technique is much cleaner than appending '>> logfile 2>&1' to every command. The exec redirection applies to all subsequent commands in the script automatically.
# Create script with dual logging
cat > /tmp/dual_log.sh << 'EOF'
#!/bin/bash
LOG_FILE="/var/log/deployment_$(date +%Y%m%d_%H%M%S).log"
# Redirect stdout and stderr to both screen and log file
exec > >(tee -a "$LOG_FILE")
exec 2>&1
echo "======================================"
echo "Deployment Script Started"
echo "Time: $(date)"
echo "Log file: $LOG_FILE"
echo "======================================"
deploy_step() {
local step_name="$1"
echo ""
echo "[$(date +%H:%M:%S)] Step: $step_name"
echo "----------------------------------------"
}
deploy_step "Stopping application"
# systemctl stop myapp
echo "Application stopped successfully"
deploy_step "Backing up current version"
# tar czf backup_$(date +%Y%m%d).tar.gz /opt/myapp
echo "Backup completed"
deploy_step "Deploying new version"
# cp new_version.jar /opt/myapp/
echo "New version deployed"
deploy_step "Starting application"
# systemctl start myapp
echo "Application started successfully"
echo ""
echo "======================================"
echo "Deployment completed at $(date)"
echo "Check log file for details: $LOG_FILE"
echo "======================================"
EOF
chmod +x /tmp/dual_log.sh
/tmp/dual_log.sh
# Output appears on screen AND is saved to log file
======================================
Deployment Script Started
Time: Sun Dec 14 14:45:30 EST 2025
Log file: /var/log/deployment_20251214_144530.log
======================================
[14:45:30] Step: Stopping application
----------------------------------------
Application stopped successfully
[14:45:30] Step: Backing up current version
----------------------------------------
Backup completed
[14:45:31] Step: Deploying new version
----------------------------------------
New version deployed
[14:45:31] Step: Starting application
----------------------------------------
Application started successfully
======================================
Deployment completed at Sun Dec 14 14:45:32 EST 2025
Check log file for details: /var/log/deployment_20251214_144530.log
======================================
The >(tee -a "$LOG_FILE") syntax uses process substitution to create a pipe to tee, which writes to both stdout (screen) and the log file. This is perfect for deployment scripts and interactive system administration tasks.
# Create script demonstrating FD save/restore
cat > /tmp/fd_management.sh << 'EOF'
#!/bin/bash
echo "This goes to normal stdout (your screen)"
# Save current stdout to file descriptor 3
exec 3>&1
# Redirect stdout to a file
exec 1>/tmp/redirected_output.txt
echo "This goes to /tmp/redirected_output.txt"
echo "So does this"
echo "Current time: $(date)"
# Restore stdout from file descriptor 3
exec 1>&3
# Close file descriptor 3 (cleanup)
exec 3>&-
echo "Back to normal stdout (your screen)"
echo "Check the file:"
cat /tmp/redirected_output.txt
# Advanced: Save both stdout and stderr
echo "=== Advanced FD Management ==="
# Save current stdout to FD 6
exec 6>&1
# Save current stderr to FD 7
exec 7>&2
# Redirect both to a combined log
exec &> /tmp/combined.log
echo "This goes to combined.log"
echo "Error message" >&2
# Restore original stdout
exec 1>&6 6>&-
# Restore original stderr
exec 2>&7 7>&-
echo "Back to screen"
echo "Combined log contents:"
cat /tmp/combined.log
EOF
chmod +x /tmp/fd_management.sh
/tmp/fd_management.sh
This goes to normal stdout (your screen)
Back to normal stdout (your screen)
Check the file:
This goes to /tmp/redirected_output.txt
So does this
Current time: Sun Dec 14 14:50:15 EST 2025
=== Advanced FD Management ===
Back to screen
Combined log contents:
This goes to combined.log
Error message
This technique uses file descriptor (FD) manipulation to save the original stdout to FD 3, redirect stdout to a file, then restore it later. FDs 0, 1, 2 are stdin, stdout, stderr. FDs 3-9 are available for custom use.
# Create a Docker-style entry point script
cat > /usr/local/bin/docker-entrypoint.sh << 'EOF'
#!/bin/bash
set -e
# Function to handle setup
setup_environment() {
echo "Setting up environment..."
# Create necessary directories
mkdir -p /var/log/app
mkdir -p /var/run/app
# Set permissions
chown -R app:app /var/log/app /var/run/app
# Generate config from environment variables
cat > /etc/app/config.yml << EOFCONFIG
database:
host: ${DB_HOST:-localhost}
port: ${DB_PORT:-5432}
name: ${DB_NAME:-myapp}
logging:
level: ${LOG_LEVEL:-INFO}
file: /var/log/app/application.log
EOFCONFIG
echo "Environment setup complete"
}
# Run setup
setup_environment
# Log startup
echo "[$(date)] Starting application as PID $$"
# Handle different commands
case "$1" in
bash|sh|/bin/bash|/bin/sh)
# If user wants a shell, give them one
echo "Starting interactive shell..."
exec "$@"
;;
"")
# No command specified, run default application
echo "Running default application..."
exec /usr/local/bin/myapp
;;
*)
# Run the specified command
echo "Running: $@"
exec "$@"
;;
esac
# Never reached
EOF
chmod +x /usr/local/bin/docker-entrypoint.sh
# In Dockerfile, this would be:
# ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# CMD ["myapp"]
# The exec ensures:
# 1. myapp becomes PID 1 (receives signals directly)
# 2. No orphaned shell process consuming resources
# 3. Proper signal handling for graceful shutdown
Setting up environment...
Environment setup complete
[Sun Dec 14 14:55:00 EST 2025] Starting application as PID 1
Running default application...
# Inside container:
$ ps aux
USER PID COMMAND
app 1 /usr/local/bin/myapp
# Note: PID 1 is myapp, not the shell script
Using exec in container entry points is critical. Without it, the shell script remains as PID 1 and your application doesn't receive signals (like SIGTERM) properly, breaking graceful shutdown. This is a Docker best practice.
# Create a universal command wrapper
cat > /usr/local/bin/logged-exec << 'EOF'
#!/bin/bash
# Wrapper that logs command execution with timestamps
if [ $# -eq 0 ]; then
echo "Usage: logged-exec <command> [args...]"
exit 1
fi
# Configuration
LOG_DIR="/var/log/command-execution"
mkdir -p "$LOG_DIR"
# Generate log filename from command name
COMMAND_NAME=$(basename "$1")
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="$LOG_DIR/${COMMAND_NAME}_${TIMESTAMP}.log"
# Log execution details
{
echo "========================================="
echo "Command Execution Log"
echo "========================================="
echo "Timestamp: $(date)"
echo "User: $USER"
echo "Working Directory: $PWD"
echo "Command: $*"
echo "PID: $$"
echo "========================================="
echo ""
} > "$LOG_FILE"
# Save original file descriptors
exec 3>&1 4>&2
# Redirect stdout and stderr to log file (appending)
exec 1>> "$LOG_FILE" 2>&1
# Print start marker
echo "[START: $(date)]"
# Execute the actual command
# Note: We use "$@" to preserve arguments properly
"$@"
EXIT_CODE=$?
# Print end marker
echo "[END: $(date)] Exit code: $EXIT_CODE"
# Restore original file descriptors
exec 1>&3 2>&4
# Close the saved descriptors
exec 3>&- 4>&-
# Print summary to screen
echo "Command executed: $*"
echo "Exit code: $EXIT_CODE"
echo "Log saved to: $LOG_FILE"
exit $EXIT_CODE
EOF
chmod +x /usr/local/bin/logged-exec
# Use the wrapper
logged-exec ls -la /etc
logged-exec grep -r "error" /var/log
logged-exec python3 my_script.py --verbose
# All executions are logged with full output
Command executed: ls -la /etc
Exit code: 0
Log saved to: /var/log/command-execution/ls_20251214_150000.log
# In the log file:
=========================================
Command Execution Log
=========================================
Timestamp: Sun Dec 14 15:00:00 EST 2025
User: craig
Working Directory: /home/craig
Command: ls -la /etc
PID: 12345
=========================================
[START: Sun Dec 14 15:00:00 EST 2025]
total 1234
drwxr-xr-x 142 root root 12288 Dec 14 14:00 .
drwxr-xr-x 20 root root 4096 Nov 28 09:12 ..
drwxr-xr-x 3 root root 4096 Oct 15 10:23 acpi
...
[END: Sun Dec 14 15:00:00 EST 2025] Exit code: 0
This wrapper doesn't use exec to replace itself because it needs to capture output and provide summary information. However, it demonstrates sophisticated FD management with exec for redirection.
# Create a signal-aware wrapper
cat > /tmp/signal_wrapper.sh << 'EOF'
#!/bin/bash
# Flag to track if we're shutting down
SHUTTING_DOWN=false
# Cleanup function
cleanup() {
echo ""
echo "Received shutdown signal..."
SHUTTING_DOWN=true
# If we haven't exec'd yet, do cleanup
if [ "$EXEC_DONE" != "true" ]; then
echo "Performing cleanup..."
# Clean up temp files, close connections, etc.
echo "Cleanup complete"
fi
exit 0
}
# Set up signal handlers
trap cleanup SIGTERM SIGINT
echo "Wrapper script starting (PID: $$)"
echo "Press Ctrl+C to test signal handling"
# Do some setup work
echo "Performing initialization..."
sleep 2
if [ "$SHUTTING_DOWN" = true ]; then
echo "Interrupted during setup"
exit 1
fi
echo "Setup complete"
# At this point, we're ready to exec
# After exec, this script is replaced and trap handlers are gone
# The new process gets default signal handling
export EXEC_DONE=true
echo "About to exec the main application..."
echo "PID will remain $$, but this script will be replaced"
# In real usage, you'd exec the actual application here
# For demonstration, we'll exec a long-running command
exec sleep 30
# Never reached
echo "This line never executes"
EOF
chmod +x /tmp/signal_wrapper.sh
# Run it and try Ctrl+C before and after exec
/tmp/signal_wrapper.sh
# Before exec: trap handler catches signal
# After exec: sleep catches signal (default behavior)
Wrapper script starting (PID: 12345)
Press Ctrl+C to test signal handling
Performing initialization...
^C
Received shutdown signal...
Performing cleanup...
Cleanup complete
# Output (if Ctrl+C pressed after exec):
Wrapper script starting (PID: 12345)
Press Ctrl+C to test signal handling
Performing initialization...
Setup complete
About to exec the main application...
PID will remain 12345, but this script will be replaced
^C
# Sleep terminates (default behavior, no custom cleanup)
When you exec, your shell script's trap handlers are lost. The new process gets default signal handling. If you need custom signal handling in the final application, implement it in that application, not in the wrapper script.
# Demonstrate exec options
# 1. Change the apparent command name with -a
cat > /tmp/test_name.sh << 'EOF'
#!/bin/bash
echo "My name is: $0"
EOF
chmod +x /tmp/test_name.sh
# Normal execution
/tmp/test_name.sh
# Output: My name is: /tmp/test_name.sh
# With exec -a to change $0
cat > /tmp/wrapper.sh << 'EOF'
#!/bin/bash
echo "Wrapper starting, my name is: $0"
# Execute test_name.sh but make it think its name is "custom_name"
exec -a "custom_name" /tmp/test_name.sh
EOF
chmod +x /tmp/wrapper.sh
/tmp/wrapper.sh
# Output:
# Wrapper starting, my name is: /tmp/wrapper.sh
# My name is: custom_name
# 2. Clear environment with -c (security/isolation)
cat > /tmp/clean_env.sh << 'EOF'
#!/bin/bash
echo "=== Current Environment ==="
export SENSITIVE_DATA="secret123"
export API_KEY="key456"
env | sort
echo ""
echo "=== Running with clean environment ==="
# -c clears all environment variables
# -l makes it a login shell (reads profile files)
exec -c env | sort
# Never reached
EOF
chmod +x /tmp/clean_env.sh
/tmp/clean_env.sh
# 3. Practical use: run command in minimal environment
cat > /tmp/secure_exec.sh << 'EOF'
#!/bin/bash
# Run a command in a minimal, controlled environment
# Useful for security, testing, or avoiding environment pollution
# Build a minimal environment
CLEAN_PATH="/usr/local/bin:/usr/bin:/bin"
CLEAN_HOME="/tmp/clean_home"
mkdir -p "$CLEAN_HOME"
# Execute with only specified variables
exec -c env -i \
PATH="$CLEAN_PATH" \
HOME="$CLEAN_HOME" \
TERM="$TERM" \
"$@"
EOF
chmod +x /tmp/secure_exec.sh
# Run a command in clean environment
/tmp/secure_exec.sh bash -c 'echo "PATH=$PATH"; echo "HOME=$HOME"; env | wc -l'
=== Current Environment ===
API_KEY=key456
HOME=/home/craig
PATH=/usr/local/bin:/usr/bin:/bin
SENSITIVE_DATA=secret123
...many more variables...
=== Running with clean environment ===
PWD=/tmp
SHLVL=1
_=/usr/bin/env
# Secure exec output:
PATH=/usr/local/bin:/usr/bin:/bin
HOME=/tmp/clean_home
3
# Only 3 environment variables instead of dozens
The -c flag is powerful for security: it prevents environment variable leakage to subprocess. The -a flag is useful for process name spoofing in ps output, which can be useful for daemon management and security obscurity.
#!/bin/bash
# File: /usr/local/bin/service-starter
# Production-ready service wrapper with comprehensive features
set -e # Exit on error
set -u # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# ============================================
# Configuration
# ============================================
SERVICE_NAME="${SERVICE_NAME:-myapp}"
SERVICE_USER="${SERVICE_USER:-app}"
SERVICE_HOME="${SERVICE_HOME:-/opt/$SERVICE_NAME}"
LOG_DIR="${LOG_DIR:-/var/log/$SERVICE_NAME}"
PID_FILE="${PID_FILE:-/var/run/$SERVICE_NAME.pid}"
CONFIG_DIR="${CONFIG_DIR:-/etc/$SERVICE_NAME}"
# ============================================
# Logging Setup
# ============================================
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="$LOG_DIR/${SERVICE_NAME}_${TIMESTAMP}.log"
# Ensure log directory exists
mkdir -p "$LOG_DIR"
# Dual logging function (before exec)
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}
# ============================================
# Error Handling
# ============================================
error_exit() {
log "ERROR" "$1"
exit 1
}
# ============================================
# Validation
# ============================================
validate_environment() {
log "INFO" "Validating environment..."
# Check if running as correct user
if [ "$EUID" -eq 0 ] && [ "$SERVICE_USER" != "root" ]; then
error_exit "This service should not run as root. Switch to $SERVICE_USER first."
fi
# Check required directories
[ -d "$SERVICE_HOME" ] || error_exit "Service home not found: $SERVICE_HOME"
[ -d "$CONFIG_DIR" ] || error_exit "Config directory not found: $CONFIG_DIR"
# Check required files
[ -f "$SERVICE_HOME/bin/application.jar" ] || error_exit "Application jar not found"
log "INFO" "Environment validation passed"
}
# ============================================
# Environment Setup
# ============================================
setup_environment() {
log "INFO" "Setting up environment..."
# Set working directory
cd "$SERVICE_HOME" || error_exit "Cannot change to service home"
# Export environment variables
export JAVA_HOME="${JAVA_HOME:-/usr/lib/jvm/java-11-openjdk}"
export PATH="$JAVA_HOME/bin:$PATH"
export APP_CONFIG="$CONFIG_DIR/application.conf"
export LOG_DIR
# JVM options
export JAVA_OPTS="-Xmx2048m -Xms1024m"
export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC"
export JAVA_OPTS="$JAVA_OPTS -Dlog.dir=$LOG_DIR"
export JAVA_OPTS="$JAVA_OPTS -Dconfig.file=$APP_CONFIG"
log "INFO" "Environment setup complete"
}
# ============================================
# Pre-flight Checks
# ============================================
preflight_checks() {
log "INFO" "Running preflight checks..."
# Check if already running
if [ -f "$PID_FILE" ]; then
OLD_PID=$(cat "$PID_FILE")
if kill -0 "$OLD_PID" 2>/dev/null; then
error_exit "Service already running with PID $OLD_PID"
else
log "WARN" "Removing stale PID file"
rm -f "$PID_FILE"
fi
fi
# Check port availability (if applicable)
if netstat -tuln | grep -q ":8080 "; then
error_exit "Port 8080 already in use"
fi
# Check disk space
AVAILABLE_SPACE=$(df -BG "$LOG_DIR" | awk 'NR==2 {print $4}' | sed 's/G//')
if [ "$AVAILABLE_SPACE" -lt 1 ]; then
error_exit "Insufficient disk space in $LOG_DIR"
fi
log "INFO" "Preflight checks passed"
}
# ============================================
# Main Execution
# ============================================
main() {
log "INFO" "========================================="
log "INFO" "Starting $SERVICE_NAME service"
log "INFO" "PID: $$"
log "INFO" "User: $(whoami)"
log "INFO" "========================================="
# Run validation and setup
validate_environment
setup_environment
preflight_checks
# Write PID file
echo $$ > "$PID_FILE"
log "INFO" "PID file created: $PID_FILE"
# Setup log rotation for this session
# (Keep last 10 log files)
find "$LOG_DIR" -name "${SERVICE_NAME}_*.log" -type f | \
sort -r | tail -n +11 | xargs rm -f 2>/dev/null || true
# Set up file descriptor redirection for the exec'd process
# All further output goes to log file
exec 1>> "$LOG_FILE" 2>&1
log "INFO" "Executing application..."
log "INFO" "Command: java $JAVA_OPTS -jar $SERVICE_HOME/bin/application.jar"
# Replace this script with the actual application
# PID remains the same, script is replaced
exec java $JAVA_OPTS -jar "$SERVICE_HOME/bin/application.jar"
# Never reached
error_exit "exec failed - this line should never execute"
}
# ============================================
# Entry Point
# ============================================
main "$@"
[2025-12-14 15:15:00] [INFO] =========================================
[2025-12-14 15:15:00] [INFO] Starting myapp service
[2025-12-14 15:15:00] [INFO] PID: 12345
[2025-12-14 15:15:00] [INFO] User: app
[2025-12-14 15:15:00] [INFO] =========================================
[2025-12-14 15:15:00] [INFO] Validating environment...
[2025-12-14 15:15:00] [INFO] Environment validation passed
[2025-12-14 15:15:00] [INFO] Setting up environment...
[2025-12-14 15:15:00] [INFO] Environment setup complete
[2025-12-14 15:15:00] [INFO] Running preflight checks...
[2025-12-14 15:15:00] [INFO] Preflight checks passed
[2025-12-14 15:15:00] [INFO] PID file created: /var/run/myapp.pid
[2025-12-14 15:15:00] [INFO] Executing application...
[2025-12-14 15:15:00] [INFO] Command: java -Xmx2048m -Xms1024m...
# Process list shows Java, not the wrapper:
$ ps aux | grep 12345
app 12345 ... java -Xmx2048m ... application.jar
This production template demonstrates best practices: validation before execution, comprehensive logging, error handling, PID file management, preflight checks, and finally exec to replace the wrapper with the actual application. Use this as a starting point for production service wrappers.
exec command directly in your interactive terminal unless you want to
replace your shell with that command. When the command finishes, your terminal closes. Always
test exec commands in scripts first.
exec 3>&1; exec 1>logfile; ...; exec 1>&3 3>&-. This allows temporary redirection.
exec command || { echo "exec failed"; exit 1; }
exec -c flag clears the environment, preventing variable leakage. Useful for
security-sensitive applications. Combine with env -i to set a minimal, controlled
environment.
exec > >(tee logfile) for dual logging (screen and file). Or redirect all
output to a log before exec: exec &>>logfile; exec application. Plan your logging
strategy before exec.