diff --git a/src/lib-sieve/sieve-common.h b/src/lib-sieve/sieve-common.h
index c832dc8650adacc06ea9926ce86e76b861b38079..508958c23a039ca9b816e4d90f066a5494212dcf 100644
--- a/src/lib-sieve/sieve-common.h
+++ b/src/lib-sieve/sieve-common.h
@@ -216,4 +216,12 @@ struct sieve_instance {
 	struct sieve_mail_sender redirect_from;
 };
 
+/*
+ * Script trace log
+ */
+
+void sieve_trace_log_write_line
+	(struct sieve_trace_log *trace_log, const string_t *line)
+	ATTR_NULL(2);
+
 #endif /* __SIEVE_COMMON_H */
diff --git a/src/lib-sieve/sieve-interpreter.c b/src/lib-sieve/sieve-interpreter.c
index a9bb38be2132531472068eefd4f3803850bdfae6..08063a3d627279499efdff883d1a9cd452afece0 100644
--- a/src/lib-sieve/sieve-interpreter.c
+++ b/src/lib-sieve/sieve-interpreter.c
@@ -130,8 +130,8 @@ static struct sieve_interpreter *_sieve_interpreter_create
 	interp->runenv.msgdata = msgdata;
 	interp->runenv.scriptenv = senv;
 
-	if ( senv->trace_stream != NULL ) {
-		interp->trace.stream = senv->trace_stream;
+	if ( senv->trace_log != NULL ) {
+		interp->trace.log = senv->trace_log;
 		interp->trace.config = senv->trace_config;
 		interp->trace.indent = 0;
 		interp->runenv.trace = &interp->trace;
diff --git a/src/lib-sieve/sieve-runtime-trace.c b/src/lib-sieve/sieve-runtime-trace.c
index c14375ef9e2170a17c9e4fd9e45250b4c5f57022..e4798bdc5d7d8b2ad2118104d60ff7ec7973f173 100644
--- a/src/lib-sieve/sieve-runtime-trace.c
+++ b/src/lib-sieve/sieve-runtime-trace.c
@@ -36,15 +36,13 @@ static inline string_t *_trace_line_new
 static inline void _trace_line_print
 (string_t *trline, const struct sieve_runtime_env *renv)
 {
-	str_append_c(trline, '\n');
-
-	o_stream_send(renv->trace->stream, str_data(trline), str_len(trline));
+	sieve_trace_log_write_line(renv->trace->log, trline);
 }
 
 static inline void _trace_line_print_empty
 (const struct sieve_runtime_env *renv)
 {
-	o_stream_send_str(renv->trace->stream, "\n");
+	sieve_trace_log_write_line(renv->trace->log, NULL);
 }
 
 /*
diff --git a/src/lib-sieve/sieve-runtime-trace.h b/src/lib-sieve/sieve-runtime-trace.h
index b479cb6a489e80e2a92a6e4b87c06abde0f0e83f..2b19c27b0ef9c650505088749ee2e2223fa320b9 100644
--- a/src/lib-sieve/sieve-runtime-trace.h
+++ b/src/lib-sieve/sieve-runtime-trace.h
@@ -13,7 +13,7 @@
 
 struct sieve_runtime_trace {
 	struct sieve_trace_config config;
-	struct ostream *stream;
+	struct sieve_trace_log *log;
 	unsigned int indent;
 };
 
diff --git a/src/lib-sieve/sieve-types.h b/src/lib-sieve/sieve-types.h
index fcc4b8719e8d5fda157e6ff80847df7cb3a79976..1c3e84674fe229c6ef60f331bc617061b5c0b4b5 100644
--- a/src/lib-sieve/sieve-types.h
+++ b/src/lib-sieve/sieve-types.h
@@ -21,6 +21,7 @@ struct sieve_binary;
 struct sieve_message_data;
 struct sieve_script_env;
 struct sieve_exec_status;
+struct sieve_trace_log;
 
 /*
  * System environment
@@ -225,7 +226,7 @@ struct sieve_script_env {
 	struct sieve_exec_status *exec_status;
 
 	/* Runtime trace*/
-	struct ostream *trace_stream;
+	struct sieve_trace_log *trace_log;
 	struct sieve_trace_config trace_config;
 };
 
diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c
index b29f5c5472c8ae9140b8c9c59c60bef030736d50..c6fe9948573bde2cbf34f38e261d457b06e5289d 100644
--- a/src/lib-sieve/sieve.c
+++ b/src/lib-sieve/sieve.c
@@ -4,9 +4,12 @@
 #include "lib.h"
 #include "str.h"
 #include "istream.h"
+#include "ostream.h"
 #include "buffer.h"
+#include "time-util.h"
 #include "eacces-error.h"
 #include "home-expand.h"
+#include "hostpid.h"
 
 #include "sieve-settings.h"
 #include "sieve-extensions.h"
@@ -796,5 +799,94 @@ size_t sieve_max_script_size(struct sieve_instance *svinst)
 	return svinst->max_script_size;
 }
 
+/*
+ * Script trace log
+ */
+
+struct sieve_trace_log {
+	struct ostream *output;
+};
+
+int sieve_trace_log_create
+(struct sieve_instance *svinst, const char *path,
+	struct sieve_trace_log **trace_log_r)
+{
+	struct sieve_trace_log *trace_log;
+	struct ostream *output;
+	int fd;
+
+	*trace_log_r = NULL;
+
+	if ( path == NULL ) {
+		output = o_stream_create_fd(1, 0, FALSE);
+	} else {
+		fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0600);
+		if ( fd == -1 ) {
+			sieve_sys_error(svinst, "trace: "
+				"creat(%s) failed: %m", path);
+			return -1;
+		}
+		output = o_stream_create_fd_autoclose(&fd, 0);
+	}
+
+	trace_log = i_new(struct sieve_trace_log, 1);
+	trace_log->output = output;
+
+	*trace_log_r = trace_log;
+	return 0;
+}
+
+int sieve_trace_log_create_dir
+(struct sieve_instance *svinst, const char *dir,
+	struct sieve_trace_log **trace_log_r)
+{
+	static unsigned int counter = 0;
+	const char *timestamp, *prefix;
+	struct stat st;
+
+	*trace_log_r = NULL;
 
+	if (stat(dir, &st) < 0) {
+		if (errno != ENOENT && errno != EACCES) {
+			sieve_sys_error(svinst, "trace: "
+				"stat(%s) failed: %m", dir);
+		}
+		return -1;
+	}
+
+	timestamp = t_strflocaltime("%Y%m%d-%H%M%S", ioloop_time);
+
+	counter++;
+	prefix = t_strdup_printf("%s/%s.%s.%u.trace",
+		dir, timestamp, my_pid, counter);
+	return sieve_trace_log_create(svinst, prefix, trace_log_r);
+}
+
+void sieve_trace_log_write_line
+(struct sieve_trace_log *trace_log, const string_t *line)
+{
+	struct const_iovec iov[2];
+
+	if (line == NULL) {
+		o_stream_send_str(trace_log->output, "\n");
+		return;
+	}
+
+	memset(iov, 0, sizeof(iov));
+	iov[0].iov_base = str_data(line);
+	iov[0].iov_len = str_len(line);
+	iov[1].iov_base = "\n";
+	iov[1].iov_len = 1;
+	o_stream_sendv(trace_log->output, iov, 2);
+}
+
+void sieve_trace_log_free(struct sieve_trace_log **_trace_log)
+{
+	struct sieve_trace_log *trace_log = *_trace_log;
+
+	*_trace_log = NULL;
+
+	o_stream_destroy(&trace_log->output);
+	i_free(trace_log);
+}
 
diff --git a/src/lib-sieve/sieve.h b/src/lib-sieve/sieve.h
index 9ed3d61a08f432c02737de8585bb739ab9b15aed..3e108e02aab253be99008c3175b5bc6013879da8 100644
--- a/src/lib-sieve/sieve.h
+++ b/src/lib-sieve/sieve.h
@@ -213,4 +213,20 @@ unsigned int sieve_max_redirects(struct sieve_instance *svinst);
 unsigned int sieve_max_actions(struct sieve_instance *svinst);
 size_t sieve_max_script_size(struct sieve_instance *svinst);
 
+/*
+ * Script trace log
+ */
+
+struct sieve_trace_log;
+
+int sieve_trace_log_create
+	(struct sieve_instance *svinst, const char *path,
+		struct sieve_trace_log **trace_log_r)
+	ATTR_NULL(2);
+int sieve_trace_log_create_dir
+	(struct sieve_instance *svinst, const char *dir,
+		struct sieve_trace_log **trace_log_r);
+
+void sieve_trace_log_free(struct sieve_trace_log **_trace_log);
+
 #endif
diff --git a/src/sieve-tools/sieve-test.c b/src/sieve-tools/sieve-test.c
index 316bfab5dbb21b4096fd5e5878993944a1ab2a45..3d884544e71432f7831a9aa9b277a8368b0dedba 100644
--- a/src/sieve-tools/sieve-test.c
+++ b/src/sieve-tools/sieve-test.c
@@ -124,7 +124,7 @@ int main(int argc, char **argv)
 	ARRAY_TYPE (const_string) scriptfiles;
 	const char *scriptfile, *recipient, *final_recipient, *sender, *mailbox,
 		*dumpfile, *tracefile, *mailfile, *mailloc;
-	struct sieve_trace_config tr_config;
+	struct sieve_trace_config trace_config;
 	struct mail *mail;
 	struct sieve_binary *main_sbin, *sbin = NULL;
 	struct sieve_message_data msgdata;
@@ -132,7 +132,7 @@ int main(int argc, char **argv)
 	struct sieve_exec_status estatus;
 	struct sieve_error_handler *ehandler, *action_ehandler;
 	struct ostream *teststream = NULL;
-	struct ostream *tracestream = NULL;
+	struct sieve_trace_log *trace_log = NULL;
 	bool force_compile = FALSE, execute = FALSE;
 	int exit_status = EXIT_SUCCESS;
 	int ret, c;
@@ -146,8 +146,8 @@ int main(int argc, char **argv)
 	/* Parse arguments */
 	recipient = final_recipient = sender = mailbox = dumpfile =
 		tracefile = mailloc = NULL;
-	memset(&tr_config, 0, sizeof(tr_config));
-	tr_config.level = SIEVE_TRLVL_ACTIONS;
+	memset(&trace_config, 0, sizeof(trace_config));
+	trace_config.level = SIEVE_TRLVL_ACTIONS;
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
 		switch (c) {
 		case 'r':
@@ -176,7 +176,7 @@ int main(int argc, char **argv)
 			break;
 			/* trace options */
 		case 'T':
-			sieve_tool_parse_trace_option(&tr_config, optarg);
+			sieve_tool_parse_trace_option(&trace_config, optarg);
 			break;
 		case 'd':
 			/* dump file */
@@ -287,8 +287,11 @@ int main(int argc, char **argv)
 					( msgdata.id == NULL ? "unspecified" : msgdata.id )));
 		}
 
-		if ( tracefile != NULL )
-			tracestream = sieve_tool_open_output_stream(tracefile);
+		if ( tracefile != NULL ) {
+			(void)sieve_trace_log_create(svinst,
+				(strcmp(tracefile, "-") == 0 ? NULL : tracefile),
+				&trace_log);
+		}
 
 		/* Compose script environment */
 		memset(&scriptenv, 0, sizeof(scriptenv));
@@ -301,8 +304,8 @@ int main(int argc, char **argv)
 		scriptenv.smtp_finish = sieve_smtp_finish;
 		scriptenv.duplicate_mark = duplicate_mark;
 		scriptenv.duplicate_check = duplicate_check;
-		scriptenv.trace_stream = tracestream;
-		scriptenv.trace_config = tr_config;
+		scriptenv.trace_log = trace_log;
+		scriptenv.trace_config = trace_config;
 		scriptenv.exec_status = &estatus;
 
 		/* Run the test */
@@ -412,6 +415,8 @@ int main(int argc, char **argv)
 
 		if ( teststream != NULL )
 			o_stream_destroy(&teststream);
+		if ( trace_log != NULL )
+			sieve_trace_log_free(&trace_log);
 
 		/* Cleanup remaining binaries */
 		if ( sbin != NULL )
diff --git a/src/testsuite/testsuite-script.c b/src/testsuite/testsuite-script.c
index e99e4e7886b7b61df740900e2fb0fdda44fa9444..e85c9ab78c5c7e4cf32e5ad799b86d50f72d3b52 100644
--- a/src/testsuite/testsuite-script.c
+++ b/src/testsuite/testsuite-script.c
@@ -116,7 +116,7 @@ bool testsuite_script_run(const struct sieve_runtime_env *renv)
 	scriptenv.duplicate_mark = NULL;
 	scriptenv.duplicate_check = NULL;
 	scriptenv.user = renv->scriptenv->user;
-	scriptenv.trace_stream = renv->scriptenv->trace_stream;
+	scriptenv.trace_log = renv->scriptenv->trace_log;
 	scriptenv.trace_config = renv->scriptenv->trace_config;
 
 	result = testsuite_result_get();
@@ -189,7 +189,7 @@ bool testsuite_script_multiscript
 	scriptenv.duplicate_mark = NULL;
 	scriptenv.duplicate_check = NULL;
 	scriptenv.user = renv->scriptenv->user;
-	scriptenv.trace_stream = renv->scriptenv->trace_stream;
+	scriptenv.trace_log = renv->scriptenv->trace_log;
 	scriptenv.trace_config = renv->scriptenv->trace_config;
 
 	/* Start execution */
diff --git a/src/testsuite/testsuite.c b/src/testsuite/testsuite.c
index 2d8f5e1a0aabdc4f7f4be3da72bc5cea0d66ea0b..38bcf84e8f964cfd9c5cb717c16298e40cab827c 100644
--- a/src/testsuite/testsuite.c
+++ b/src/testsuite/testsuite.c
@@ -84,7 +84,7 @@ int main(int argc, char **argv)
 {
 	struct sieve_instance *svinst;
 	const char *scriptfile, *dumpfile, *tracefile;
-	struct sieve_trace_config tr_config;
+	struct sieve_trace_config trace_config;
 	struct sieve_binary *sbin;
 	const char *sieve_dir;
 	bool log_stdout = FALSE;
@@ -95,8 +95,8 @@ int main(int argc, char **argv)
 
 	/* Parse arguments */
 	dumpfile = tracefile = NULL;
-	memset(&tr_config, 0, sizeof(tr_config));
-	tr_config.level = SIEVE_TRLVL_ACTIONS;
+	memset(&trace_config, 0, sizeof(trace_config));
+	trace_config.level = SIEVE_TRLVL_ACTIONS;
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
 		switch (c) {
 		case 'd':
@@ -108,7 +108,7 @@ int main(int argc, char **argv)
 			tracefile = optarg;
 			break;
 		case 'T':
-			sieve_tool_parse_trace_option(&tr_config, optarg);
+			sieve_tool_parse_trace_option(&trace_config, optarg);
 			break;
 		case 'E':
 			log_stdout = TRUE;
@@ -162,14 +162,17 @@ int main(int argc, char **argv)
 	if ( (sbin = sieve_compile
 		(svinst, scriptfile, NULL, testsuite_log_main_ehandler, 0, NULL))
 			!= NULL ) {
-		struct ostream *tracestream = NULL;
+		struct sieve_trace_log *trace_log = NULL;
 		struct sieve_script_env scriptenv;
 
 		/* Dump script */
 		sieve_tool_dump_binary_to(sbin, dumpfile, FALSE);
 
-		if ( tracefile != NULL )
-			tracestream = sieve_tool_open_output_stream(tracefile);
+		if ( tracefile != NULL ) {
+			(void)sieve_trace_log_create(svinst,
+				(strcmp(tracefile, "-") == 0 ? NULL : tracefile),
+				&trace_log);
+		}
 
 		testsuite_mailstore_init();
 		testsuite_message_init();
@@ -182,8 +185,8 @@ int main(int argc, char **argv)
 		scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
 		scriptenv.smtp_send = testsuite_smtp_send;
 		scriptenv.smtp_finish = testsuite_smtp_finish;
-		scriptenv.trace_stream = tracestream;
-		scriptenv.trace_config = tr_config;
+		scriptenv.trace_log = trace_log;
+		scriptenv.trace_config = trace_config;
 
 		testsuite_scriptenv = &scriptenv;
 
@@ -213,8 +216,8 @@ int main(int argc, char **argv)
 		testsuite_mailstore_deinit();
 		testsuite_result_deinit();
 
-		if ( tracestream != NULL )
-			o_stream_unref(&tracestream);
+		if ( trace_log != NULL )
+			sieve_trace_log_free(&trace_log);
 
 		testsuite_scriptenv = NULL;
 	} else {