diff --git a/README.md b/README.md
index 63b3f23..0816dca 100644
--- a/README.md
+++ b/README.md
@@ -215,6 +215,11 @@ optional arguments:
 	                    AWS_DEFAULT_REGION env variable, or if that is not
 	                    set, the value in `~/.aws/config`. As a last resort,
 	                    it will use us-east-1
+  --log-level LOG_LEVEL
+                        Set the log level, default WARNING
+  --log-file LOG_FILE    Set the log output file, default credstash.log. Errors
+                        are printed to stderr and stack traces are logged to
+                        file                      
   -t TABLE, --table TABLE
 	                    DynamoDB table to use for credential storage
   -p PROFILE, --profile PROFILE
diff --git a/credstash.py b/credstash.py
index 46333a5..05f5eb6 100755
--- a/credstash.py
+++ b/credstash.py
@@ -25,6 +25,7 @@
 import re
 import boto3
 import botocore.exceptions
+import logging
 
 try:
     from StringIO import StringIO
@@ -66,6 +67,20 @@
 WILDCARD_CHAR = "*"
 THREAD_POOL_MAX_SIZE = 64
 
+logger = logging.getLogger('credstash')
+
+def setup_logging(level, log_file):
+    if logger.hasHandlers():
+        for h in logger.handlers:
+            logger.removeHandler(h)
+    handler = logging.FileHandler(log_file)
+    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler) 
+    logger.setLevel(level)
+
+# setup logging with default values when imported as a lib
+setup_logging(logging.WARNING, 'credstash.log')
 
 class KeyService(object):
 
@@ -247,10 +262,15 @@ def func_wrapper(*args, **kwargs):
             return func(*args, **kwargs)
         except botocore.exceptions.ClientError as e:
             print(str(e), file=sys.stderr)
-            sys.exit(1)
+            logger.exception(e)
+        except Exception as e:
+            print(str(e), file=sys.stderr)
+            logger.exception(e)
+        sys.exit(1)
     return func_wrapper
 
 
+@clean_fail
 def listSecrets(region=None, table="credential-store", session=None, **kwargs):
     '''
     do a full-table scan of the credential-store,
@@ -279,7 +299,7 @@ def listSecrets(region=None, table="credential-store", session=None, **kwargs):
 
     return items
 
-
+@clean_fail
 def putSecret(name, secret, version="", kms_key="alias/credstash",
               region=None, table="credential-store", context=None,
               digest=DEFAULT_DIGEST, comment="", kms=None, dynamodb=None, **kwargs):
@@ -490,7 +510,7 @@ def getSecretAction(args, region, **session_params):
     except IntegrityError as e:
         fatal(e)
 
-
+@clean_fail
 def getSecret(name, version="", region=None,
               table="credential-store", context=None,
               dynamodb=None, kms=None, **kwargs):
@@ -809,6 +829,15 @@ def get_parser():
                                   "CREDSTASH_DEFAULT_TABLE env variable, "
                                   "or if that is not set, the value "
                                   "`credential-store` will be used")
+    parsers['super'].add_argument("--log-level", 
+        help="Set the log level, default WARNING",
+        default='WARNING'
+    )
+    parsers['super'].add_argument("--log-file",
+        help="Set the log output file, default credstash.log. Errors are "
+        "printed to stderr and stack traces are logged to file",
+        default='credstash.log'
+    )
     role_parse = parsers['super'].add_mutually_exclusive_group()
     role_parse.add_argument("-p", "--profile", default=None,
                             help="Boto config profile to use when "
@@ -978,9 +1007,13 @@ def main():
     parsers = get_parser()
     args = parsers['super'].parse_args()
 
+    # setup logging
+    setup_logging(args.log_level, args.log_file)
+
     # Check for assume role and set  session params
     session_params = get_session_params(args.profile, args.arn)
 
+    # test for region
     try:
         region = args.region
         session = get_session(**session_params)