diff --git before/doc/aide.1 after/doc/aide.1
index a7b9462..5d10e33 100644
--- before/doc/aide.1
+++ after/doc/aide.1
@@ -122,12 +122,25 @@ SIGUSR1 toggles the log_level between current and debug level.
 .PP
 .SH NOTES
 
+.IP "Checksum encoding"
+
 The checksums in the database and in the output are by default base64
 encoded (see also report_base16 option).
 To decode them you can use the following shell command:
 
 echo <encoded_checksum> | base64 \-d | hexdump \-v \-e '32/1 "%02x" "\\n"'
 
+.IP "Control characters"
+
+Control characters (00-31 and 127) are always escaped in log and plain report
+output. They are escaped by a literal backslash (\\) followed by exactly 3
+digits representing the character in octal notation (e.g. a newline is output
+as "\fB\\012\fR"). A literal backslash is not escaped unless it is followed by
+3 digits (0-9), in this case the literal backslash is escaped as
+"\fB\\134\fR". Reports in JSON format are escaped according to the JSON specs
+(e.g. a newline is output as "\fB\\b\fR" or an escape (\fBESC\fR) is output as
+"\fB\\u001b\fR")
+
 .PP
 .SH FILES
 
diff --git before/include/util.h after/include/util.h
index aaff780..d339740 100644
--- before/include/util.h
+++ after/include/util.h
@@ -51,6 +51,9 @@ int cmpurl(url_t*, url_t*);
 
 int contains_unsafe(const char*);
 
+char *strnesc(const char *, size_t);
+char *stresc(const char *);
+
 void decode_string(char*);
 
 char* encode_string(const char*);
diff --git before/src/aide.c after/src/aide.c
index b04e721..576fbd3 100644
--- before/src/aide.c
+++ after/src/aide.c
@@ -271,7 +271,8 @@ static void read_param(int argc,char**argv)
                 conf->limit=checked_malloc(strlen(optarg)+1);
                 strcpy(conf->limit,optarg);
                 if((conf->limit_crx=pcre_compile(conf->limit, PCRE_ANCHORED, &pcre_error, &pcre_erroffset, NULL)) == NULL) {
-                    INVALID_ARGUMENT("--limit", error in regular expression '%s' at %i: %s, conf->limit, pcre_erroffset, pcre_error)
+                    char * limit_safe = stresc(conf->limit);
+                    INVALID_ARGUMENT("--limit", error in regular expression '%s' at %i: %s, limit_safe, pcre_erroffset, pcre_error)
                 }
                 log_msg(LOG_LEVEL_INFO,_("(--limit): set limit to '%s'"), conf->limit);
             break;
@@ -572,7 +573,11 @@ int main(int argc,char**argv)
       rx_rule* rule = NULL;
       int match = check_rxtree(conf->check_path, conf->tree, &rule, conf->check_file_type, true);
       if (match < 0) {
-        fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", get_restriction_char(conf->check_file_type), conf->check_path, conf->limit);
+        char* limit_safe = stresc(conf->limit);
+        char* path_safe = stresc(conf->check_path);
+        fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", get_restriction_char(conf->check_file_type), path_safe, limit_safe);
+        free(path_safe);
+        free(limit_safe);
         exit(2);
       } else {
         exit(match?0:1);
diff --git before/src/gen_list.c after/src/gen_list.c
index 98b437c..7059899 100644
--- before/src/gen_list.c
+++ after/src/gen_list.c
@@ -510,7 +510,9 @@ int check_rxtree(char* filename,seltree* tree, rx_rule* *rule, RESTRICTION_TYPE
   int match = check_seltree(tree, filename, file_type, rule);
   if (dry_run) {
       char * str;
-      fprintf(stdout, "[%c] %c '%s': ", match?'X':' ', get_restriction_char(file_type), filename);
+      char* filename_safe = stresc(filename);
+      fprintf(stdout, "[%c] %c '%s': ", match?'X':' ', get_restriction_char(file_type), filename_safe);
+      free(filename_safe);
       if (match > 0) {
           char* attr_str;
           fprintf(stdout, "%s: '%s%s %s %s' (%s:%d: '%s')\n", get_rule_type_long_string(match), match == EQUAL_MATCH?"=":"", (*rule)->rx, str = get_restriction_string((*rule)->restriction), attr_str = diff_attributes(0, (*rule)->attr), (*rule)->config_filename, (*rule)->config_linenumber, (*rule)->config_line);
diff --git before/src/log.c after/src/log.c
index 5a25a36..5d7fa74 100644
--- before/src/log.c
+++ after/src/log.c
@@ -25,6 +25,7 @@
 
 #include "log.h"
 #include "locale-aide.h"
+#include "util.h"
 
 LOG_LEVEL prev_log_level = LOG_LEVEL_UNSET;
 LOG_LEVEL log_level = LOG_LEVEL_UNSET;
@@ -60,7 +61,7 @@ static void cache_line(LOG_LEVEL level, const char* format, va_list ap) {
 
     void * tmp = realloc(cached_lines, (ncachedlines+1) * sizeof(log_cache)); /* freed in log_cached_lines() */
     if (tmp == NULL) {
-        log_msg(LOG_LEVEL_ERROR, "realloc() failed: %s", strerror(errno));
+        fprintf(stderr, "%s: realloc: failed to allocate memory\n", log_level_array[LOG_LEVEL_ERROR-1].log_string);
         exit(EXIT_FAILURE);
     } else {
         cached_lines = tmp;
@@ -85,7 +86,12 @@ const char * get_log_level_name(LOG_LEVEL level) {
 
 static void log_cached_lines(void) {
     for(int i = 0; i < ncachedlines; ++i) {
-        log_msg(cached_lines[i].level, "%s", cached_lines[i].message);
+        LOG_LEVEL level = cached_lines[i].level;
+        if (level == LOG_LEVEL_ERROR || level <= log_level) {
+            char * msg_safe = stresc(cached_lines[i].message);
+            fprintf(stderr, "%s: %s\n", log_level_array[level-1].log_string, msg_safe);
+            free(msg_safe);
+        }
         free(cached_lines[i].message);
     }
     ncachedlines = 0;
@@ -96,9 +102,24 @@ static void vlog_msg(LOG_LEVEL level,const char* format, va_list ap) {
     FILE* url = stderr;
 
     if (level == LOG_LEVEL_ERROR || level <= log_level) {
-        fprintf(url, "%s: ", log_level_array[level-1].log_string );
-        vfprintf(url, format, ap);
-        fprintf(url, "\n");
+
+        va_list aq;
+        va_copy(aq, ap);
+        size_t n = vsnprintf(NULL, 0, format, aq) + 1;
+        va_end(aq);
+
+        int size = n * sizeof(char);
+        char *msg_unsafe = malloc(size);
+        if (msg_unsafe == NULL) {
+            fprintf(stderr, "%s: malloc: failed to allocate %d bytes of memory\n", log_level_array[LOG_LEVEL_ERROR-1].log_string, size);
+            exit(EXIT_FAILURE);
+        }
+
+        vsnprintf(msg_unsafe, n, format, ap);
+        char *msg_safe = stresc(msg_unsafe);
+        free(msg_unsafe);
+        fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, msg_safe);
+        free(msg_safe);
     } else if (log_level == LOG_LEVEL_UNSET) {
         cache_line(level, format, ap);
     }
diff --git before/src/report.c after/src/report.c
index b342a0f..e3cee48 100644
--- before/src/report.c
+++ after/src/report.c
@@ -631,6 +631,7 @@ if ((conf->action&(DO_COMPARE|DO_DIFF) || (conf->action&DO_INIT && r->detailed_i
 #endif
             )) {
 
+    char *filename_safe = stresc(((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename);
     if(r->summarize_changes) {
         int i;
         char* summary = checked_malloc ((report_attrs_order_length+1) * sizeof (char));
@@ -676,17 +677,18 @@ if ((conf->action&(DO_COMPARE|DO_DIFF) || (conf->action&DO_INIT && r->detailed_i
             }
         }
         summary[report_attrs_order_length]='\0';
-        report_printf(r, "\n%s: %s", summary, ((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename);
+        report_printf(r, "\n%s: %s", summary, filename_safe);
         free(summary); summary=NULL;
     } else {
         if (node->checked&NODE_ADDED) {
-            report_printf(r,_("\nadded: %s"),(node->new_data)->filename);
+            report_printf(r,_("\nadded: %s"), filename_safe);
         } else if (node->checked&NODE_REMOVED) {
-            report_printf(r,_("\nremoved: %s"),(node->old_data)->filename);
+            report_printf(r,_("\nremoved: %s"), filename_safe);
         } else if (node->checked&NODE_CHANGED) {
-            report_printf(r,_("\nchanged: %s"),(node->new_data)->filename);
+            report_printf(r,_("\nchanged: %s"), filename_safe);
         }
     }
+    free(filename_safe);
             }
         } else {
             break; /* list sorted by report_level */
@@ -698,36 +700,49 @@ if ((conf->action&(DO_COMPARE|DO_DIFF) || (conf->action&DO_INIT && r->detailed_i
 static void print_attribute(REPORT_LEVEL report_level, db_line* oline, db_line* nline,
         DB_ATTR_TYPE attr, report_t *r, const char* name,
         DB_ATTR_TYPE report_attrs, DB_ATTR_TYPE added_attrs, DB_ATTR_TYPE removed_attrs) {
-    char **ovalue, **nvalue;
+    char **ovalues = NULL;
+    char **nvalues = NULL;
     int onumber, nnumber, olen, nlen, i, k, c;
     int p = (width_details-(4 + MAX_WIDTH_DETAILS_STRING))/2;
 
         if ( (attr&report_attrs && r->level >= report_level)
           || (report_attrs && attr&(added_attrs|removed_attrs) && r->level >= REPORT_LEVEL_ADDED_REMOVED_ATTRIBUTES) ) {
 
-            onumber=get_attribute_values(attr, oline, &ovalue, r);
-            nnumber=get_attribute_values(attr, nline, &nvalue, r);
+            onumber=get_attribute_values(attr, oline, &ovalues, r);
+            nnumber=get_attribute_values(attr, nline, &nvalues, r);
 
             i = 0;
             while (i<onumber || i<nnumber) {
-                olen = i<onumber?strlen(ovalue[i]):0;
-                nlen = i<nnumber?strlen(nvalue[i]):0;
+                char *ovalue = NULL;
+                char *nvalue = NULL;
+                olen = 0;
+                nlen = 0;
+                if (i<onumber){
+                    ovalue = stresc(ovalues[i]);
+                    olen = strlen(ovalue);
+                }
+                if (i<nnumber) {
+                    nvalue = stresc(nvalues[i]);
+                    nlen = strlen(nvalue);
+                }
                 k = 0;
                 while (olen-p*k >= 0 || nlen-p*k >= 0) {
                     c = k*(p-1);
                     if (!onumber) {
-                        report_printf(r," %-*s%c %-*c  %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, ' ', p-1, nlen-c>0?&nvalue[i][c]:"");
+                        report_printf(r," %-*s%c %-*c  %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, ' ', p-1, nlen-c>0?&nvalue[c]:"");
                     } else if (!nnumber) {
-                        report_printf(r," %-*s%c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p-1, olen-c>0?&ovalue[i][c]:"");
+                        report_printf(r," %-*s%c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p-1, olen-c>0?&ovalue[c]:"");
                     } else {
-                        report_printf(r," %-*s%c %-*.*s| %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, p-1, olen-c>0?&ovalue[i][c]:"", p-1, nlen-c>0?&nvalue[i][c]:"");
+                        report_printf(r," %-*s%c %-*.*s| %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, p-1, olen-c>0?&ovalue[c]:"", p-1, nlen-c>0?&nvalue[c]:"");
                     }
                     k++;
                 }
                 ++i;
+                free(ovalue);
+                free(nvalue);
             }
-            for(i=0; i < onumber; ++i) { free(ovalue[i]); ovalue[i]=NULL; } free(ovalue); ovalue=NULL;
-            for(i=0; i < nnumber; ++i) { free(nvalue[i]); nvalue[i]=NULL; } free(nvalue); nvalue=NULL;
+            for(i=0; i < onumber; ++i) { free(ovalues[i]); ovalues[i]=NULL; } free(ovalues); ovalues=NULL;
+            for(i=0; i < nnumber; ++i) { free(nvalues[i]); nvalues[i]=NULL; } free(nvalues); nvalues=NULL;
         }
 }
 
@@ -760,7 +775,9 @@ static void print_dbline_attributes(REPORT_LEVEL report_level, db_line* oline, d
             if (file_type) {
                 report_printf(r, "%s: ", file_type);
             }
-            report_printf(r, "%s\n", (nline==NULL?oline:nline)->filename);
+            char *filename_safe = stresc((nline==NULL?oline:nline)->filename);
+            report_printf(r, "%s\n", filename_safe);
+            free(filename_safe);
         }
 
     for (int j=0; j < report_attrs_order_length; ++j) {
@@ -829,9 +846,11 @@ static void terse_report(seltree* node) {
             if (!(node->checked&(NODE_MOVED_IN|NODE_MOVED_OUT))){
                 if (r->level >= REPORT_LEVEL_LIST_ENTRIES
                   && (node->old_data->attr&~(r->ignore_removed_attrs))^(node->new_data->attr&~(r->ignore_added_attrs)) ) {
+                    char *entry_safe = stresc(node->old_data->filename);
                     char *str = NULL;
                     report_printf(r, "Entry %s in databases has different attributes: %s\n",
-                            node->old_data->filename,str= diff_attributes(node->old_data->attr&~(r->ignore_removed_attrs),node->new_data->attr&~(r->ignore_added_attrs)));
+                            entry_safe,str= diff_attributes(node->old_data->attr&~(r->ignore_removed_attrs),node->new_data->attr&~(r->ignore_added_attrs)));
+                    free(entry_safe);
                     free(str);
                 }
                 DB_ATTR_TYPE changed_attrs = (node->changed_attrs)&~(r->ignore_changed_attrs);
@@ -933,14 +952,18 @@ static void print_report_header() {
         if (r->level >= REPORT_LEVEL_SUMMARY) {
             int first = 1;
             if (conf->limit != NULL) {
-                report_printf(r, _("Limit: %s"), conf->limit);
+                char *limit_safe = stresc(conf->limit);
+                report_printf(r, _("Limit: %s"), limit_safe);
+                free(limit_safe);
                 first = 0;
             }
 
             if (conf->action&(DO_INIT|DO_COMPARE) && conf->root_prefix_length > 0) {
                 if (first) { first=0; }
                 else { report_printf(r," | "); }
-                report_printf(r, _("Root prefix: %s"),conf->root_prefix);
+                char *prefix_safe = stresc(conf->root_prefix);
+                report_printf(r, _("Root prefix: %s"), prefix_safe);
+                free(prefix_safe);
             }
 
             if (r->level != REPORT_LEVEL_CHANGED_ATTRIBUTES) {
diff --git before/src/util.c after/src/util.c
index 1826059..ba7359e 100644
--- before/src/util.c
+++ after/src/util.c
@@ -99,6 +99,40 @@ int cmpurl(url_t* u1,url_t* u2)
   return RETOK;
 };
 
+static size_t escape_str(const char *unescaped_str, char *str, size_t s) {
+    size_t n = 0;
+    size_t i = 0;
+    char c;
+    while (i < s && (c = unescaped_str[i])) {
+        if ((c >= 0 && (c < 0x1f || c == 0x7f)) ||
+            (c == '\\' && isdigit(unescaped_str[i+1])
+                       && isdigit(unescaped_str[i+2])
+                       && isdigit(unescaped_str[i+3])
+                ) ) {
+            if (str) { snprintf(&str[n], 5, "\\%03o", c); }
+            n += 4;
+        } else {
+            if (str) { str[n] = c; }
+            n++;
+        }
+        i++;
+    }
+    if (str) { str[n] = '\0'; }
+    n++;
+    return n;
+}
+
+char *strnesc(const char *unescaped_str, size_t s) {
+    int n = escape_str(unescaped_str, NULL, s);
+    char *str = checked_malloc(n);
+    escape_str(unescaped_str, str, s);
+    return str;
+}
+
+char *stresc(const char *unescaped_str) {
+    return strnesc(unescaped_str, strlen(unescaped_str));
+}
+
 /* Returns 1 if the string contains unsafe characters, 0 otherwise.  */
 int contains_unsafe (const char *s)
 {
